Skip to content

Commit f47225a

Browse files
committed
# This is a combination of 2 commits.
# This is the 1st commit message: first sdk v2 version of login events # This is the commit message #2: # squash! first sdk v2 version of login events
1 parent 9a2b575 commit f47225a

File tree

4 files changed

+364
-23
lines changed

4 files changed

+364
-23
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// <copyright file="EventTrackingSdkV2.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#nullable enable
7+
using Datadog.Trace.SourceGenerators;
8+
9+
namespace Datadog.Trace.AppSec;
10+
11+
/// <summary>
12+
/// Allow
13+
/// </summary>
14+
public static class EventTrackingSdkV2
15+
{
16+
/// <summary>
17+
/// Sets the details of a successful login on the local root span
18+
/// </summary>
19+
/// <param name="userLogin">The userLogin associated with the login success</param>
20+
[Instrumented]
21+
public static void TrackUserLoginSuccess(string userLogin)
22+
{
23+
}
24+
25+
/// <summary>
26+
/// Sets the details of a successful login on the local root span
27+
/// </summary>
28+
/// <param name="userLogin">The userLogin associated with the login success</param>
29+
/// <param name="metadata">The optional metadata associated with the login success</param>
30+
[Instrumented]
31+
public static void TrackUserLoginSuccess(string userLogin, IDictionary<string, string> metadata)
32+
{
33+
}
34+
35+
/// <summary>
36+
/// Sets the details of a successful login on the local root span
37+
/// </summary>
38+
/// <param name="userLogin">The userLogin associated with the login success</param>
39+
/// <param name="userId">The optional userId associated with the login success</param>
40+
/// <param name="metadata">The optional metadata associated with the login success</param>
41+
[Instrumented]
42+
public static void TrackUserLoginSuccess(string userLogin, string userId, IDictionary<string, string>? metadata = null)
43+
{
44+
}
45+
46+
/// <summary>
47+
/// Sets the details of a successful logon on the local root span
48+
/// </summary>
49+
/// <param name="userLogin">The userId associated with the login success</param>
50+
/// <param name="userDetails">The userLogin associated with the login success</param>
51+
/// <param name="metadata">Metadata associated with the login success</param>
52+
[Instrumented]
53+
public static void TrackUserLoginSuccess(string userLogin, UserDetails userDetails, IDictionary<string, string>? metadata = null)
54+
{
55+
}
56+
57+
/// <summary>
58+
/// Sets the details of a logon failure on the local root span
59+
/// </summary>
60+
/// <param name="userLogin">The userId associated with the login failure</param>
61+
/// <param name="exists">If the userId associated with the login failure exists</param>
62+
/// <param name="metadata">Metadata associated with the login failure</param>
63+
[Instrumented]
64+
public static void TrackUserLoginFailure(string userLogin, bool exists, IDictionary<string, string>? metadata = null)
65+
{
66+
}
67+
68+
/// <summary>
69+
/// Sets the details of a logon failure on the local root span
70+
/// </summary>
71+
/// <param name="userLogin">The user login associated with the login failure</param>
72+
/// <param name="exists">If the userId associated with the login failure exists</param>
73+
/// <param name="userDetails">The details of the user associated with the login failure</param>
74+
/// <param name="metadata">Metadata associated with the login failure</param>
75+
[Instrumented]
76+
public static void TrackUserLoginFailure(string userLogin, bool exists, UserDetails userDetails, IDictionary<string, string>? metadata = null)
77+
{
78+
}
79+
}
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
// <copyright file="EventTrackingSdkV2.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#nullable enable
7+
using System;
8+
using System.Collections.Generic;
9+
using System.Runtime.CompilerServices;
10+
using System.Text;
11+
using Datadog.Trace.AppSec.Coordinator;
12+
using Datadog.Trace.SourceGenerators;
13+
using Datadog.Trace.Tagging;
14+
using Datadog.Trace.Telemetry;
15+
using Datadog.Trace.Telemetry.Metrics;
16+
17+
namespace Datadog.Trace.AppSec;
18+
19+
/// <summary>
20+
/// Allow
21+
/// </summary>
22+
public static class EventTrackingSdkV2
23+
{
24+
/// <summary>
25+
/// Sets the details of a successful login on the local root span
26+
/// </summary>
27+
/// <param name="userLogin">The userLogin associated with the login success</param>
28+
[PublicApi]
29+
public static void TrackUserLoginSuccess(string userLogin)
30+
{
31+
TelemetryFactory.Metrics.Record(PublicApiUsage.EventTrackingSdk_TrackUserLoginSuccessEvent);
32+
TelemetryFactory.Metrics.RecordCountUserEventSdk(MetricTags.UserEventSdk.UserEventLoginSuccessSdkV2);
33+
TrackUserLoginSuccess(userLogin, null, null, Tracer.Instance);
34+
}
35+
36+
/// <summary>
37+
/// Sets the details of a successful login on the local root span
38+
/// </summary>
39+
/// <param name="userLogin">The userLogin associated with the login success</param>
40+
/// <param name="metadata">The optional metadata associated with the login success</param>
41+
[PublicApi]
42+
public static void TrackUserLoginSuccess(string userLogin, IDictionary<string, string> metadata)
43+
{
44+
TelemetryFactory.Metrics.Record(PublicApiUsage.EventTrackingSdk_TrackUserLoginSuccessEvent);
45+
TelemetryFactory.Metrics.RecordCountUserEventSdk(MetricTags.UserEventSdk.UserEventLoginSuccessSdkV2);
46+
TrackUserLoginSuccess(userLogin, null, metadata, Tracer.Instance);
47+
}
48+
49+
/// <summary>
50+
/// Sets the details of a successful login on the local root span
51+
/// </summary>
52+
/// <param name="userLogin">The userLogin associated with the login success</param>
53+
/// <param name="userId">The optional userId associated with the login success</param>
54+
/// <param name="metadata">The optional metadata associated with the login success</param>
55+
[PublicApi]
56+
public static void TrackUserLoginSuccess(string userLogin, string userId, IDictionary<string, string>? metadata = null)
57+
{
58+
TelemetryFactory.Metrics.Record(PublicApiUsage.EventTrackingSdk_TrackUserLoginSuccessEvent);
59+
TelemetryFactory.Metrics.RecordCountUserEventSdk(MetricTags.UserEventSdk.UserEventLoginSuccessSdkV2);
60+
TrackUserLoginSuccess(userLogin, new UserDetails(userId), metadata, Tracer.Instance);
61+
}
62+
63+
/// <summary>
64+
/// Sets the details of a successful logon on the local root span
65+
/// </summary>
66+
/// <param name="userLogin">The userId associated with the login success</param>
67+
/// <param name="userDetails">The userLogin associated with the login success</param>
68+
/// <param name="metadata">Metadata associated with the login success</param>
69+
[PublicApi]
70+
public static void TrackUserLoginSuccess(string userLogin, UserDetails userDetails, IDictionary<string, string>? metadata = null)
71+
{
72+
TelemetryFactory.Metrics.Record(PublicApiUsage.EventTrackingSdk_TrackUserLoginSuccessEvent_Metadata);
73+
TelemetryFactory.Metrics.RecordCountUserEventSdk(MetricTags.UserEventSdk.UserEventLoginSuccessSdkV2);
74+
TrackUserLoginSuccess(userLogin, userDetails, metadata, Tracer.Instance);
75+
}
76+
77+
private static void TrackUserLoginSuccess(string userLogin, UserDetails? userDetails, IDictionary<string, string>? metadata, Tracer tracer)
78+
{
79+
if (string.IsNullOrEmpty(userLogin))
80+
{
81+
ThrowHelper.ThrowArgumentNullException(nameof(userLogin));
82+
}
83+
84+
var span = tracer.ActiveScope?.Span;
85+
86+
if (span is null)
87+
{
88+
ThrowHelper.ThrowException("Can't create a tracking event with no active span");
89+
}
90+
91+
var setTag = TaggingUtils.GetSpanSetter(span, out var internalSpan);
92+
if (internalSpan is null)
93+
{
94+
ThrowHelper.ThrowException("Can't create a tracking event without a span setter found");
95+
}
96+
97+
setTag(Tags.AppSec.EventsUsers.LoginEvent.SuccessLogin, userLogin);
98+
setTag(Tags.AppSec.EventsUsers.LoginEvent.SuccessTrack, Tags.AppSec.EventsUsers.True);
99+
setTag(Tags.AppSec.EventsUsers.LoginEvent.SuccessSdkSource, Tags.AppSec.EventsUsers.True);
100+
101+
PopulateTags(userDetails, metadata, setTag, true);
102+
103+
RunSecurityChecksAndReport(internalSpan, userLogin: userLogin, userId: userDetails?.Id, loginSuccess: true);
104+
}
105+
106+
/// <summary>
107+
/// Sets the details of a logon failure on the local root span
108+
/// </summary>
109+
/// <param name="userLogin">The userId associated with the login failure</param>
110+
/// <param name="exists">If the userId associated with the login failure exists</param>
111+
/// <param name="metadata">Metadata associated with the login failure</param>
112+
[PublicApi]
113+
public static void TrackUserLoginFailure(string userLogin, bool exists, IDictionary<string, string>? metadata = null)
114+
{
115+
TelemetryFactory.Metrics.Record(PublicApiUsage.EventTrackingSdk_TrackUserLoginFailureEvent_Metadata);
116+
TelemetryFactory.Metrics.RecordCountUserEventSdk(MetricTags.UserEventSdk.UserEventFailureSdkV2);
117+
TrackUserLoginFailure(userLogin, exists, userDetails: null, metadata, Tracer.Instance);
118+
}
119+
120+
/// <summary>
121+
/// Sets the details of a logon failure on the local root span
122+
/// </summary>
123+
/// <param name="userLogin">The user login associated with the login failure</param>
124+
/// <param name="exists">If the userId associated with the login failure exists</param>
125+
/// <param name="userDetails">The details of the user associated with the login failure</param>
126+
/// <param name="metadata">Metadata associated with the login failure</param>
127+
[PublicApi]
128+
public static void TrackUserLoginFailure(string userLogin, bool exists, UserDetails userDetails, IDictionary<string, string>? metadata = null)
129+
{
130+
TelemetryFactory.Metrics.Record(PublicApiUsage.EventTrackingSdk_TrackUserLoginFailureEvent_Metadata);
131+
TelemetryFactory.Metrics.RecordCountUserEventSdk(MetricTags.UserEventSdk.UserEventFailureSdkV2);
132+
TrackUserLoginFailure(userLogin, exists, userDetails, metadata, Tracer.Instance);
133+
}
134+
135+
private static void TrackUserLoginFailure(string userLogin, bool exists, UserDetails? userDetails, IDictionary<string, string>? metadata, Tracer tracer)
136+
{
137+
if (string.IsNullOrEmpty(userLogin))
138+
{
139+
ThrowHelper.ThrowArgumentNullException(nameof(userLogin));
140+
}
141+
142+
var span = tracer.ActiveScope?.Span;
143+
144+
if (span is null)
145+
{
146+
ThrowHelper.ThrowException("Can't create a tracking event with no active span");
147+
}
148+
149+
var setTag = TaggingUtils.GetSpanSetter(span, out var internalSpan);
150+
if (internalSpan is null)
151+
{
152+
ThrowHelper.ThrowException("Can't create a tracking event without a span setter found");
153+
}
154+
155+
setTag(Tags.AppSec.EventsUsers.LoginEvent.FailureTrack, Tags.AppSec.EventsUsers.True);
156+
setTag(Tags.AppSec.EventsUsers.LoginEvent.FailureSdkSource, Tags.AppSec.EventsUsers.True);
157+
setTag(Tags.AppSec.EventsUsers.LoginEvent.FailureUserId, userLogin);
158+
setTag(Tags.AppSec.EventsUsers.LoginEvent.FailureUserExists, exists ? Tags.AppSec.EventsUsers.True : Tags.AppSec.EventsUsers.False);
159+
setTag(Tags.AppSec.EventsUsers.LoginEvent.FailureUserLogin, userLogin);
160+
161+
PopulateTags(userDetails, metadata, setTag, false);
162+
163+
RunSecurityChecksAndReport(internalSpan, userLogin: userLogin, loginSuccess: false);
164+
}
165+
166+
private static void PopulateTags(UserDetails? userDetails, IDictionary<string, string>? metadata, Action<string, string> setTag, bool loginSuccess)
167+
{
168+
setTag(Tags.AppSec.EventsUsers.CollectionMode, Tags.AppSec.EventsUsers.Sdk);
169+
170+
var prefix = loginSuccess ? Tags.AppSec.EventsUsers.LoginEvent.Success : Tags.AppSec.EventsUsers.LoginEvent.Failure;
171+
var prefixUser = prefix + ".usr";
172+
if (metadata is { Count: > 0 })
173+
{
174+
foreach (var kvp in metadata)
175+
{
176+
setTag($"{prefix}.{kvp.Key}", kvp.Value);
177+
}
178+
}
179+
180+
if (userDetails is { } userDetailsValue)
181+
{
182+
// usr.id should always be set, even when PropagateId is true
183+
setTag(Tags.User.Id, userDetailsValue.Id);
184+
setTag(Tags.AppSec.EventsUsers.CollectionMode, Tags.AppSec.EventsUsers.Sdk);
185+
setTag(prefixUser + ".id", userDetailsValue.Id);
186+
187+
if (userDetailsValue.PropagateId)
188+
{
189+
var base64UserId = Convert.ToBase64String(Encoding.UTF8.GetBytes(userDetailsValue.Id));
190+
const string propagatedUserIdTag = TagPropagation.PropagatedTagPrefix + Tags.User.Id;
191+
setTag(propagatedUserIdTag, base64UserId);
192+
setTag(prefixUser + ".propagate_id", Tags.AppSec.EventsUsers.True);
193+
}
194+
else
195+
{
196+
setTag(prefixUser + ".propagate_id", Tags.AppSec.EventsUsers.False);
197+
}
198+
199+
if (userDetailsValue.Name is not null)
200+
{
201+
setTag(prefixUser + ".name", userDetailsValue.Name);
202+
setTag(Tags.User.Name, userDetailsValue.Name);
203+
}
204+
205+
if (userDetailsValue.Scope is not null)
206+
{
207+
setTag(Tags.User.Scope, userDetailsValue.Scope);
208+
setTag(prefixUser + ".scope", userDetailsValue.Scope);
209+
}
210+
211+
if (userDetailsValue.Role is not null)
212+
{
213+
setTag(Tags.User.Role, userDetailsValue.Role);
214+
setTag(prefixUser + ".role", userDetailsValue.Role);
215+
}
216+
217+
if (userDetailsValue.SessionId is not null)
218+
{
219+
setTag(Tags.User.SessionId, userDetailsValue.SessionId);
220+
setTag(prefixUser + ".session_id", userDetailsValue.SessionId);
221+
}
222+
223+
if (userDetailsValue.Email is not null)
224+
{
225+
setTag(Tags.User.Email, userDetailsValue.Email);
226+
setTag(prefixUser + ".email", userDetailsValue.Email);
227+
}
228+
}
229+
}
230+
231+
/// <summary>
232+
/// Biggest part of this method will only work in a web context
233+
/// </summary>
234+
/// <param name="span">span</param>
235+
/// <param name="userLogin">now mandatory user login</param>
236+
/// <param name="userId">user id</param>
237+
/// <param name="loginSuccess">whether it's a login success</param>
238+
private static void RunSecurityChecksAndReport(Span span, string userLogin, string? userId = null, bool loginSuccess = false)
239+
{
240+
var securityInstance = Security.Instance;
241+
if (!securityInstance.IsTrackUserEventsEnabled)
242+
{
243+
return;
244+
}
245+
246+
securityInstance.SetTraceSamplingPriority(span);
247+
248+
var securityCoordinator = SecurityCoordinator.TryGetSafe(securityInstance, span);
249+
if (securityCoordinator is not null)
250+
{
251+
RunWafAndCollectHeaders();
252+
}
253+
254+
return;
255+
256+
[MethodImpl(MethodImplOptions.NoInlining)]
257+
void RunWafAndCollectHeaders()
258+
{
259+
securityCoordinator.Value.Reporter.CollectHeaders();
260+
var result = securityCoordinator.Value.RunWafForUser(userLogin: userLogin, userId: userId, fromSdk: true, otherTags: new() { { loginSuccess ? AddressesConstants.UserBusinessLoginSuccess : AddressesConstants.UserBusinessLoginFailure, string.Empty } });
261+
securityCoordinator.Value.BlockAndReport(result);
262+
}
263+
}
264+
}

tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/ManualInstrumentation/Extensions/SpanExtensionsSetUserIntegration.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,11 @@ internal static CallTargetState OnMethodBegin<TTarget, TSpan>(ref TSpan span, st
4343
// Not likely, but technically possible for this to happen
4444
realSpan = autoSpan;
4545
}
46-
else
47-
{
48-
// This is a worst case, should basically never be necessary
49-
// Only required if customers create a custom ISpan
50-
// Should we handle it? I chose to just ignore it here because it's a pain
51-
// but we could throw, or log?
52-
}
5346

47+
// This is a worst case, should basically never be necessary
48+
// Only required if customers create a custom ISpan
49+
// Should we handle it? I chose to just ignore it here because it's a pain
50+
// but we could throw, or log?
5451
realSpan?.SetUserInternal(
5552
new UserDetails(id)
5653
{

0 commit comments

Comments
 (0)