Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Extend ModuleApi with the methods we'll need to reject spam based on …IP - resolves #10832 #10833

Merged
merged 6 commits into from
Sep 22, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/10833.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Extend the ModuleApi to let plug-ins check whether an ID is local and to access IP + User Agent data.
66 changes: 65 additions & 1 deletion synapse/module_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
List,
Optional,
Tuple,
Union,
)

import jinja2
Expand All @@ -46,7 +47,14 @@
from synapse.storage.database import DatabasePool, LoggingTransaction
from synapse.storage.databases.main.roommember import ProfileInfo
from synapse.storage.state import StateFilter
from synapse.types import JsonDict, Requester, UserID, UserInfo, create_requester
from synapse.types import (
DomainSpecificString,
JsonDict,
Requester,
UserID,
UserInfo,
create_requester,
)
from synapse.util import Clock
from synapse.util.caches.descriptors import cached

Expand Down Expand Up @@ -79,6 +87,22 @@
logger = logging.getLogger(__name__)


class UserIpAndAgent:
"""
A ip, user_agent pair used by a user to connect to this homeserver.
Yoric marked this conversation as resolved.
Show resolved Hide resolved
"""

ip: str
user_agent: str
# The time at which this user agent/ip was last seen.
last_seen: int

def __init__(self, ip: str, user_agent: str, last_seen: int):
self.ip = ip
self.user_agent = user_agent
self.last_seen = last_seen
Yoric marked this conversation as resolved.
Show resolved Hide resolved


class ModuleApi:
"""A proxy object that gets passed to various plugin modules so they
can register new users etc if necessary.
Expand Down Expand Up @@ -700,6 +724,46 @@ def read_templates(
(td for td in (self.custom_template_dir, custom_template_directory) if td),
)

def is_mine(self, id: Union[str, DomainSpecificString]) -> bool:
"""Checks whether an ID comes from this homeserver."""
Yoric marked this conversation as resolved.
Show resolved Hide resolved
if isinstance(id, DomainSpecificString):
return self._hs.is_mine(id)
else:
return self._hs.is_mine_id(id)

async def get_user_ip_and_agents(
self, user_id: str, since_ts: Optional[float] = None
) -> List[UserIpAndAgent]:
"""
Return the list of user IPs and agents for a user.

Only useful for local users.
"""
Yoric marked this conversation as resolved.
Show resolved Hide resolved
# Don't hit the db if this is not a local user.
is_mine = False
try:
# Let's be defensive against ill-formed strings.
if self.is_mine(user_id):
is_mine = True
except Exception:
pass
if is_mine:
Yoric marked this conversation as resolved.
Show resolved Hide resolved
raw_data = await self._store.get_user_ip_and_agents(
UserID.from_string(user_id)
)
# Sanitize some of the data. We don't want to return tokens.
return [
UserIpAndAgent(
ip=str(data["ip"]),
user_agent=str(data["user_agent"]),
last_seen=int(data["last_seen"]),
)
for data in raw_data
if since_ts is None or int(data["last_seen"]) >= since_ts
]
else:
return []
babolivier marked this conversation as resolved.
Show resolved Hide resolved


class PublicRoomListManager:
"""Contains methods for adding to, removing from and querying whether a room
Expand Down
39 changes: 39 additions & 0 deletions tests/module_api/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def prepare(self, reactor, clock, homeserver):
self.module_api = homeserver.get_module_api()
self.event_creation_handler = homeserver.get_event_creation_handler()
self.sync_handler = homeserver.get_sync_handler()
self.auth_handler = homeserver.get_auth_handler()

def make_homeserver(self, reactor, clock):
return self.setup_test_homeserver(
Expand Down Expand Up @@ -89,6 +90,44 @@ def test_get_userinfo_by_id__no_user_found(self):
found_user = self.get_success(self.module_api.get_userinfo_by_id("@alice:test"))
self.assertIsNone(found_user)

def test_get_user_ip_and_agents(self):
user_id = self.register_user("test_get_user_ip_and_agents_user", "1234")
info = self.get_success(self.module_api.get_user_ip_and_agents(user_id))
self.assertEqual(info, [])

self.get_success(
self.store.insert_client_ip(
user_id, "access_token", "ip_1", "user_agent_1", None
)
)
self.get_success(
self.store.insert_client_ip(
user_id, "access_token", "ip_2", "user_agent_2", None
)
)
info = self.get_success(self.module_api.get_user_ip_and_agents(user_id))

self.assertEqual(len(info), 2)
ip_1_seen = False
ip_2_seen = False
for i in info:
if i.ip == "ip_1":
ip_1_seen = True
self.assertEqual(i.user_agent, "user_agent_1")
elif i.ip == "ip_2":
ip_2_seen = True
self.assertEqual(i.user_agent, "user_agent_2")
self.assertTrue(ip_1_seen)
self.assertTrue(ip_2_seen)

def test_get_user_ip_and_agents__no_user_found(self):
info = self.get_success(
self.module_api.get_user_ip_and_agents(
"@test_get_user_ip_and_agents_user_nonexistent:example.com"
)
)
self.assertEqual(info, [])

def test_sending_events_into_room(self):
"""Tests that a module can send events into a room"""
# Mock out create_and_send_nonmember_event to check whether events are being sent
Expand Down