From 9e90cc178e64a5d29373bc09582df0fe2b4b189d Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Fri, 10 Dec 2021 20:03:20 +0200 Subject: [PATCH] Allow tracking puppeted users for MAU Currently when puppeting another user, the user doing the puppeting is tracked for client ip's and MAU (if configured). When tracking MAU is important, it becomes necessary to be possible to also track the client IP's and MAU of puppeted users. As an example a client that manages user creation and creation of tokens via the Synapse admin API, passing those tokens for the client to use. This PR adds optional configuration to enable tracking of puppeted users into monthly active users. The default behaviour stays the same. Signed-off-by: Jason Robinson --- changelog.d/11561.feature | 1 + docs/sample_config.yaml | 5 +++++ synapse/api/auth.py | 13 +++++++++++++ synapse/config/server.py | 6 ++++++ tests/api/test_auth.py | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 58 insertions(+) create mode 100644 changelog.d/11561.feature diff --git a/changelog.d/11561.feature b/changelog.d/11561.feature new file mode 100644 index 000000000000..6b81a8ed6176 --- /dev/null +++ b/changelog.d/11561.feature @@ -0,0 +1 @@ +Add config flag `mau_track_puppeted_users` to allow tracking puppeted users in terms of monthly active users. diff --git a/docs/sample_config.yaml b/docs/sample_config.yaml index 6696ed5d1ef9..c3152c2b84a4 100644 --- a/docs/sample_config.yaml +++ b/docs/sample_config.yaml @@ -439,6 +439,11 @@ manhole_settings: # - medium: 'email' # address: 'reserved_user@example.com' +# If enabled, puppeted users 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. +#mau_track_puppeted_users: false + # Used by phonehome stats to group together related servers. #server_context: context diff --git a/synapse/api/auth.py b/synapse/api/auth.py index 44883c6663ff..af5e63e339f7 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -73,6 +73,7 @@ def __init__(self, hs: "HomeServer"): self._track_appservice_user_ips = hs.config.appservice.track_appservice_user_ips self._macaroon_secret_key = hs.config.key.macaroon_secret_key self._force_tracing_for_users = hs.config.tracing.force_tracing_for_users + self._mau_track_puppeted_users = hs.config.server.mau_track_puppeted_users async def check_user_in_room( self, @@ -208,6 +209,18 @@ async def get_user_by_req( 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._mau_track_puppeted_users + ): + 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/server.py b/synapse/config/server.py index 1de2dea9b024..254a2c121093 100644 --- a/synapse/config/server.py +++ b/synapse/config/server.py @@ -418,6 +418,7 @@ def read_config(self, config, **kwargs): self.mau_trial_days = config.get("mau_trial_days", 0) self.mau_limit_alerting = config.get("mau_limit_alerting", True) + self.mau_track_puppeted_users = config.get("mau_track_puppeted_users", False) # How long to keep redacted events in the database in unredacted form # before redacting them. @@ -1128,6 +1129,11 @@ def generate_config_section( # - medium: 'email' # address: 'reserved_user@example.com' + # If enabled, puppeted users 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. + #mau_track_puppeted_users: false + # Used by phonehome stats to group together related servers. #server_context: context diff --git a/tests/api/test_auth.py b/tests/api/test_auth.py index 3aa9ba3c43ac..c603eb004fab 100644 --- a/tests/api/test_auth.py +++ b/tests/api/test_auth.py @@ -210,6 +210,39 @@ def test_get_user_by_req_appservice_valid_token_bad_user_id(self): request.requestHeaders.getRawHeaders = mock_getRawHeaders() self.get_failure(self.auth.get_user_by_req(request), AuthError) + 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._mau_track_puppeted_users = 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")