Skip to content

Commit cbd2a7d

Browse files
feat(aap): add new track_user_id (#14910)
## Description To align with other tracers and to allow our customers to track a user without the login, the ATO SDK is enriched with a new `track_user_id` that only requires the user id . ## Testing Tests updated for the new function/. ## Additional Notes Public documentation should be updated soon after the merge of this PR. APPSEC-59673
1 parent 78765b4 commit cbd2a7d

File tree

3 files changed

+82
-1
lines changed

3 files changed

+82
-1
lines changed

ddtrace/appsec/track_user_sdk.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,57 @@ def track_user(
127127
raise BlockingException(_get_blocked())
128128

129129

130+
def track_user_id(
131+
user_id: t.Any,
132+
session_id: t.Optional[str] = None,
133+
metadata: t.Optional[t.Dict[str, t.Any]] = None,
134+
_auto: bool = False,
135+
):
136+
"""
137+
Track an authenticated user with only user id.
138+
139+
This function should be called when a user is authenticated in the application."
140+
"""
141+
span = _asm_request_context.get_entry_span()
142+
if span is None:
143+
return
144+
if user_id:
145+
span.set_tag_str(_constants.APPSEC.USER_LOGIN_USERID, str(user_id))
146+
meta = metadata or {}
147+
usr_name = meta.pop("name", None) or meta.pop("usr.name", None)
148+
usr_email = meta.pop("email", None) or meta.pop("usr.email", None)
149+
usr_scope = meta.pop("scope", None) or meta.pop("usr.scope", None)
150+
usr_role = meta.pop("role", None) or meta.pop("usr.role", None)
151+
_trace_utils.set_user(
152+
None,
153+
user_id,
154+
name=usr_name if isinstance(usr_name, str) else None,
155+
email=usr_email if isinstance(usr_email, str) else None,
156+
scope=usr_scope if isinstance(usr_scope, str) else None,
157+
role=usr_role if isinstance(usr_role, str) else None,
158+
session_id=session_id,
159+
span=span,
160+
may_block=_auto,
161+
mode=_constants.LOGIN_EVENTS_MODE.AUTO if _auto else _constants.LOGIN_EVENTS_MODE.SDK,
162+
)
163+
if meta:
164+
_trace_utils.track_custom_event(None, "auth_sdk", metadata=meta)
165+
if not _auto:
166+
span.set_tag_str(_constants.APPSEC.AUTO_LOGIN_EVENTS_COLLECTION_MODE, _constants.LOGIN_EVENTS_MODE.SDK)
167+
if _asm_request_context.in_asm_context():
168+
custom_data = {
169+
"REQUEST_USER_ID": str(user_id) if user_id else None,
170+
"LOGIN_SUCCESS": "sdk",
171+
}
172+
if session_id:
173+
custom_data["REQUEST_SESSION_ID"] = session_id
174+
res = _asm_request_context.call_waf_callback(custom_data=custom_data, force_sent=True)
175+
if res and any(
176+
action in [_WAF_ACTIONS.BLOCK_ACTION, _WAF_ACTIONS.REDIRECT_ACTION] for action in res.actions
177+
):
178+
raise BlockingException(_get_blocked())
179+
180+
130181
def track_custom_event(event_name: str, metadata: t.Dict[str, t.Any]):
131182
"""
132183
Track a custom user event.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
features:
3+
- |
4+
AAP: This introduces ``track_user_id`` in the ATO SDK, which is equivalent to ``track_user`` but does not require the login, only the user id.

tests/appsec/appsec/test_appsec_trace_utils.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from ddtrace.appsec.trace_utils import track_user_login_success_event
1515
from ddtrace.appsec.trace_utils import track_user_signup_event
1616
from ddtrace.appsec.track_user_sdk import track_user
17+
from ddtrace.appsec.track_user_sdk import track_user_id
1718
from ddtrace.contrib.internal.trace_utils import set_user
1819
from ddtrace.ext import user
1920
import ddtrace.internal.telemetry
@@ -275,7 +276,7 @@ def test_set_user_blocked(self):
275276
def test_track_user_blocked(self):
276277
with asm_context(tracer=self.tracer, span_name="fake_span", config=config_good_rules) as span:
277278
track_user(
278-
self.tracer,
279+
"login",
279280
user_id=self._BLOCKED_USER,
280281
session_id="usr.session_id",
281282
metadata={
@@ -298,6 +299,31 @@ def test_track_user_blocked(self):
298299
assert span.get_tag("usr.id") == str(self._BLOCKED_USER)
299300
assert is_blocked(span)
300301

302+
def test_track_user_id_blocked(self):
303+
with asm_context(tracer=self.tracer, span_name="fake_span", config=config_good_rules) as span:
304+
track_user_id(
305+
self._BLOCKED_USER,
306+
session_id="usr.session_id",
307+
metadata={
308+
"email": "usr.email",
309+
"name": "usr.name",
310+
"role": "usr.role",
311+
"scope": "usr.scope",
312+
},
313+
)
314+
assert span.get_tag(user.ID)
315+
assert span.get_tag(user.EMAIL)
316+
assert span.get_tag(user.SESSION_ID)
317+
assert span.get_tag(user.NAME)
318+
assert span.get_tag(user.ROLE)
319+
assert span.get_tag(user.SCOPE)
320+
assert span.get_tag(user.SESSION_ID)
321+
assert span.get_tag(APPSEC.AUTO_LOGIN_EVENTS_COLLECTION_MODE) == LOGIN_EVENTS_MODE.SDK
322+
# assert metadata tags are not set for usual data
323+
assert span.get_tag("appsec.events.auth_sdk.track") is None
324+
assert span.get_tag("usr.id") == str(self._BLOCKED_USER)
325+
assert is_blocked(span)
326+
301327
def test_no_span_doesnt_raise(self):
302328
from ddtrace.trace import tracer
303329

0 commit comments

Comments
 (0)