豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit ab5e5a7

Browse files
committed
Keycloak telemetry: add keycloakCallerClientId to PostHog events.
Expose Keycloak top-level clientId (introspection caller) separately from attributed clientId so Stalwart vs send-backend flows remain distinguishable when both resolve to the same token client. Tests cover stalwart vs thunderbird-send-backend introspection.
1 parent 2fa7177 commit ab5e5a7

File tree

2 files changed

+33
-29
lines changed

2 files changed

+33
-29
lines changed

src/thunderbird_accounts/telemetry/tasks.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,13 +199,16 @@ def poll_keycloak_events(self):
199199
# correct client at the top level and have no `token_issued_for`
200200
# in details, so the fallback path returns the unchanged value.
201201
details = event.get('details') or {}
202-
client_id = details.get('token_issued_for') or event.get('clientId')
202+
caller_client_id = event.get('clientId')
203+
# Attributed client (e.g. desktop): prefer token_issued_for on introspection.
204+
client_id = details.get('token_issued_for') or caller_client_id
203205

204206
capture(
205207
event=event_name,
206208
keycloak_user_id=user_id,
207209
properties={
208210
'clientId': client_id,
211+
'keycloakCallerClientId': caller_client_id,
209212
'keycloak_event_type': kc_type,
210213
'keycloak_event_id': event_id,
211214
'is_error': kc_type.endswith('_ERROR'),

src/thunderbird_accounts/telemetry/tests_keycloak.py

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -126,23 +126,32 @@ def test_seen_ids_are_persisted_to_cache(self):
126126
self.assertIn('new-event-123', cached)
127127

128128
def test_is_error_flag_for_all_event_types(self):
129-
"""Exercise every mapped event type through the task and verify is_error matches the _ERROR suffix."""
130-
self._set_keycloak_events([_make_keycloak_event(kc_type) for kc_type in settings.KEYCLOAK_EVENT_MAP])
129+
"""Every mapped event type is submitted with the correct is_error flag."""
130+
# Hardcoded so the test catches regressions in the _ERROR suffix rule
131+
# (instead of re-deriving it the same way the production code does).
132+
expected_is_error = {
133+
'LOGIN': False,
134+
'LOGIN_ERROR': True,
135+
'REGISTER': False,
136+
'REGISTER_ERROR': True,
137+
'LOGOUT': False,
138+
'CODE_TO_TOKEN': False,
139+
'CODE_TO_TOKEN_ERROR': True,
140+
'INTROSPECT_TOKEN': False,
141+
'REFRESH_TOKEN': False,
142+
}
143+
# If a new type is added to KEYCLOAK_EVENT_MAP, extend the table above.
144+
self.assertEqual(set(expected_is_error), set(settings.KEYCLOAK_EVENT_MAP))
145+
146+
self._set_keycloak_events([_make_keycloak_event(kc_type) for kc_type in expected_is_error])
131147

132148
poll_keycloak_events()
133149

134-
submitted_types = {
150+
submitted = {
135151
call.kwargs['properties']['keycloak_event_type']: call.kwargs['properties']['is_error']
136152
for call in self.mock_ph.capture.call_args_list
137153
}
138-
for kc_type in settings.KEYCLOAK_EVENT_MAP:
139-
self.assertIn(kc_type, submitted_types, f'{kc_type} was not submitted')
140-
expected_error = kc_type.endswith('_ERROR')
141-
self.assertEqual(
142-
submitted_types[kc_type],
143-
expected_error,
144-
f'{kc_type}: expected is_error={expected_error}, got {submitted_types[kc_type]}',
145-
)
154+
self.assertEqual(submitted, expected_is_error)
146155

147156
@override_settings(KEYCLOAK_EVENTS_PAGE_SIZE=3)
148157
def test_pagination_fetches_all_events(self):
@@ -154,10 +163,10 @@ def test_pagination_fetches_all_events(self):
154163
self.assertEqual(mock_kc_client.request.call_count, 3) # pages: 3+3+1
155164

156165
def test_client_id_resolution(self):
157-
"""clientId is taken from details.token_issued_for when present, else from the top-level clientId."""
166+
"""clientId is attributed (token_issued_for or top-level); keycloakCallerClientId is raw Keycloak clientId."""
158167
cases = [
159168
(
160-
'INTROSPECT_TOKEN uses details.token_issued_for',
169+
'INTROSPECT_TOKEN: attributed to token_issued_for, caller stays stalwart',
161170
_make_keycloak_event(
162171
'INTROSPECT_TOKEN',
163172
client_id='stalwart',
@@ -169,38 +178,30 @@ def test_client_id_resolution(self):
169178
},
170179
),
171180
'desktop',
181+
'stalwart',
172182
),
173183
(
174-
'LOGIN has no details; top-level clientId wins',
184+
'LOGIN has no details; top-level clientId wins for both fields',
175185
_make_keycloak_event('LOGIN', client_id='desktop'),
176186
'desktop',
177-
),
178-
(
179-
'REFRESH_TOKEN details without token_issued_for; top-level clientId wins',
180-
_make_keycloak_event(
181-
'REFRESH_TOKEN',
182-
client_id='desktop',
183-
details={
184-
'token_id': 'ofrtrt:fake',
185-
'grant_type': 'refresh_token',
186-
'refresh_token_type': 'Offline',
187-
},
188-
),
189187
'desktop',
190188
),
191189
(
192190
'no top-level clientId and no token_issued_for',
193191
_make_keycloak_event('LOGIN', include_client_id=False),
194192
None,
193+
None,
195194
),
196195
]
197196

198-
for label, event, expected_client_id in cases:
197+
for label, event, expected_client_id, expected_caller in cases:
199198
with self.subTest(case=label):
200199
self.mock_ph.reset_mock()
201200
cache.delete(settings.KEYCLOAK_SEEN_EVENTS_CACHE_KEY)
202201
self._set_keycloak_events([event])
203202

204203
poll_keycloak_events()
205204

206-
self.assertEqual(self._last_capture_properties()['clientId'], expected_client_id)
205+
props = self._last_capture_properties()
206+
self.assertEqual(props['clientId'], expected_client_id)
207+
self.assertEqual(props['keycloakCallerClientId'], expected_caller)

0 commit comments

Comments
 (0)