diff --git a/changelog.d/11561.feature b/changelog.d/11561.feature
new file mode 100644
index 0000000000..19dada883b
--- /dev/null
+++ b/changelog.d/11561.feature
@@ -0,0 +1 @@
+Add `track_puppeted_user_ips` config flag to track puppeted user IP addresses. This also includes them in monthly active user counts.
diff --git a/docs/sample_config.yaml b/docs/sample_config.yaml
index 810a14b077..26894fae34 100644
--- a/docs/sample_config.yaml
+++ b/docs/sample_config.yaml
@@ -1503,6 +1503,12 @@ room_prejoin_state:
#additional_event_types:
# - org.example.custom.event.type
+# If enabled, puppeted user IP's can also be tracked. By default when
+# puppeting another user, the user who has created the access token
+# for puppeting is tracked. If this is enabled, both requests are tracked.
+# Implicitly enables MAU tracking for puppeted users.
+#track_puppeted_user_ips: false
+
# A list of application service config files to use
#
diff --git a/synapse/api/auth.py b/synapse/api/auth.py
index 4a32d430bd..683241201c 100644
--- a/synapse/api/auth.py
+++ b/synapse/api/auth.py
@@ -71,6 +71,7 @@ class Auth:
self._auth_blocking = AuthBlocking(self.hs)
self._track_appservice_user_ips = hs.config.appservice.track_appservice_user_ips
+ self._track_puppeted_user_ips = hs.config.api.track_puppeted_user_ips
self._macaroon_secret_key = hs.config.key.macaroon_secret_key
self._force_tracing_for_users = hs.config.tracing.force_tracing_for_users
@@ -246,6 +247,18 @@ class Auth:
user_agent=user_agent,
device_id=device_id,
)
+ # Track also the puppeted user client IP if enabled and the user is puppeting
+ if (
+ user_info.user_id != user_info.token_owner
+ and self._track_puppeted_user_ips
+ ):
+ await self.store.insert_client_ip(
+ user_id=user_info.user_id,
+ access_token=access_token,
+ ip=ip_addr,
+ user_agent=user_agent,
+ device_id=device_id,
+ )
if is_guest and not allow_guest:
raise AuthError(
diff --git a/synapse/config/api.py b/synapse/config/api.py
index 25538b82d5..bdbe9f0fa2 100644
--- a/synapse/config/api.py
+++ b/synapse/config/api.py
@@ -29,6 +29,7 @@ class ApiConfig(Config):
def read_config(self, config: JsonDict, **kwargs):
validate_config(_MAIN_SCHEMA, config, ())
self.room_prejoin_state = list(self._get_prejoin_state_types(config))
+ self.track_puppeted_user_ips = config.get("track_puppeted_user_ips", False)
def generate_config_section(cls, **kwargs) -> str:
formatted_default_state_types = "\n".join(
@@ -59,6 +60,12 @@ class ApiConfig(Config):
#
#additional_event_types:
# - org.example.custom.event.type
+
+ # If enabled, puppeted user IP's can also be tracked. By default when
+ # puppeting another user, the user who has created the access token
+ # for puppeting is tracked. If this is enabled, both requests are tracked.
+ # Implicitly enables MAU tracking for puppeted users.
+ #track_puppeted_user_ips: false
""" % {
"formatted_default_state_types": formatted_default_state_types
}
@@ -138,5 +145,8 @@ _MAIN_SCHEMA = {
"properties": {
"room_prejoin_state": _ROOM_PREJOIN_STATE_CONFIG_SCHEMA,
"room_invite_state_types": _ROOM_INVITE_STATE_TYPES_SCHEMA,
+ "track_puppeted_user_ips": {
+ "type": "boolean",
+ },
},
}
diff --git a/tests/api/test_auth.py b/tests/api/test_auth.py
index a2dfa1ed05..4b53b6d40b 100644
--- a/tests/api/test_auth.py
+++ b/tests/api/test_auth.py
@@ -274,6 +274,39 @@ class AuthTestCase(unittest.HomeserverTestCase):
self.assertEquals(failure.value.code, 400)
self.assertEquals(failure.value.errcode, Codes.EXCLUSIVE)
+ def test_get_user_by_req__puppeted_token__not_tracking_puppeted_mau(self):
+ self.store.get_user_by_access_token = simple_async_mock(
+ TokenLookupResult(
+ user_id="@baldrick:matrix.org",
+ device_id="device",
+ token_owner="@admin:matrix.org",
+ )
+ )
+ self.store.insert_client_ip = simple_async_mock(None)
+ request = Mock(args={})
+ request.getClientIP.return_value = "127.0.0.1"
+ request.args[b"access_token"] = [self.test_token]
+ request.requestHeaders.getRawHeaders = mock_getRawHeaders()
+ self.get_success(self.auth.get_user_by_req(request))
+ self.store.insert_client_ip.assert_called_once()
+
+ def test_get_user_by_req__puppeted_token__tracking_puppeted_mau(self):
+ self.auth._track_puppeted_user_ips = True
+ self.store.get_user_by_access_token = simple_async_mock(
+ TokenLookupResult(
+ user_id="@baldrick:matrix.org",
+ device_id="device",
+ token_owner="@admin:matrix.org",
+ )
+ )
+ self.store.insert_client_ip = simple_async_mock(None)
+ request = Mock(args={})
+ request.getClientIP.return_value = "127.0.0.1"
+ request.args[b"access_token"] = [self.test_token]
+ request.requestHeaders.getRawHeaders = mock_getRawHeaders()
+ self.get_success(self.auth.get_user_by_req(request))
+ self.assertEquals(self.store.insert_client_ip.call_count, 2)
+
def test_get_user_from_macaroon(self):
self.store.get_user_by_access_token = simple_async_mock(
TokenLookupResult(user_id="@baldrick:matrix.org", device_id="device")
|