You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

474 lines
17 KiB

  1. # Copyright 2015, 2016 OpenMarket Ltd
  2. # Copyright 2018 New Vector Ltd
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import logging
  16. import re
  17. from typing import (
  18. TYPE_CHECKING,
  19. Any,
  20. Dict,
  21. List,
  22. Optional,
  23. Pattern,
  24. Sequence,
  25. Tuple,
  26. cast,
  27. )
  28. from synapse.appservice import (
  29. ApplicationService,
  30. ApplicationServiceState,
  31. AppServiceTransaction,
  32. TransactionOneTimeKeysCount,
  33. TransactionUnusedFallbackKeys,
  34. )
  35. from synapse.config.appservice import load_appservices
  36. from synapse.events import EventBase
  37. from synapse.storage._base import db_to_json
  38. from synapse.storage.database import (
  39. DatabasePool,
  40. LoggingDatabaseConnection,
  41. LoggingTransaction,
  42. )
  43. from synapse.storage.databases.main.events_worker import EventsWorkerStore
  44. from synapse.storage.databases.main.roommember import RoomMemberWorkerStore
  45. from synapse.storage.types import Cursor
  46. from synapse.storage.util.sequence import build_sequence_generator
  47. from synapse.types import DeviceListUpdates, JsonMapping
  48. from synapse.util import json_encoder
  49. from synapse.util.caches.descriptors import _CacheContext, cached
  50. if TYPE_CHECKING:
  51. from synapse.server import HomeServer
  52. logger = logging.getLogger(__name__)
  53. def _make_exclusive_regex(
  54. services_cache: List[ApplicationService],
  55. ) -> Optional[Pattern]:
  56. # We precompile a regex constructed from all the regexes that the AS's
  57. # have registered for exclusive users.
  58. exclusive_user_regexes = [
  59. regex.pattern
  60. for service in services_cache
  61. for regex in service.get_exclusive_user_regexes()
  62. ]
  63. if exclusive_user_regexes:
  64. exclusive_user_regex = "|".join("(" + r + ")" for r in exclusive_user_regexes)
  65. exclusive_user_pattern: Optional[Pattern] = re.compile(exclusive_user_regex)
  66. else:
  67. # We handle this case specially otherwise the constructed regex
  68. # will always match
  69. exclusive_user_pattern = None
  70. return exclusive_user_pattern
  71. class ApplicationServiceWorkerStore(RoomMemberWorkerStore):
  72. def __init__(
  73. self,
  74. database: DatabasePool,
  75. db_conn: LoggingDatabaseConnection,
  76. hs: "HomeServer",
  77. ):
  78. self.services_cache = load_appservices(
  79. hs.hostname, hs.config.appservice.app_service_config_files
  80. )
  81. self.exclusive_user_regex = _make_exclusive_regex(self.services_cache)
  82. def get_max_as_txn_id(txn: Cursor) -> int:
  83. logger.warning("Falling back to slow query, you should port to postgres")
  84. txn.execute(
  85. "SELECT COALESCE(max(txn_id), 0) FROM application_services_txns"
  86. )
  87. return cast(Tuple[int], txn.fetchone())[0]
  88. self._as_txn_seq_gen = build_sequence_generator(
  89. db_conn,
  90. database.engine,
  91. get_max_as_txn_id,
  92. "application_services_txn_id_seq",
  93. table="application_services_txns",
  94. id_column="txn_id",
  95. )
  96. super().__init__(database, db_conn, hs)
  97. def get_app_services(self) -> List[ApplicationService]:
  98. return self.services_cache
  99. def get_if_app_services_interested_in_user(self, user_id: str) -> bool:
  100. """Check if the user is one associated with an app service (exclusively)"""
  101. if self.exclusive_user_regex:
  102. return bool(self.exclusive_user_regex.match(user_id))
  103. else:
  104. return False
  105. def get_app_service_by_user_id(self, user_id: str) -> Optional[ApplicationService]:
  106. """Retrieve an application service from their user ID.
  107. All application services have associated with them a particular user ID.
  108. There is no distinguishing feature on the user ID which indicates it
  109. represents an application service. This function allows you to map from
  110. a user ID to an application service.
  111. Args:
  112. user_id: The user ID to see if it is an application service.
  113. Returns:
  114. The application service or None.
  115. """
  116. for service in self.services_cache:
  117. if service.sender == user_id:
  118. return service
  119. return None
  120. def get_app_service_by_token(self, token: str) -> Optional[ApplicationService]:
  121. """Get the application service with the given appservice token.
  122. Args:
  123. token: The application service token.
  124. Returns:
  125. The application service or None.
  126. """
  127. for service in self.services_cache:
  128. if service.token == token:
  129. return service
  130. return None
  131. def get_app_service_by_id(self, as_id: str) -> Optional[ApplicationService]:
  132. """Get the application service with the given appservice ID.
  133. Args:
  134. as_id: The application service ID.
  135. Returns:
  136. The application service or None.
  137. """
  138. for service in self.services_cache:
  139. if service.id == as_id:
  140. return service
  141. return None
  142. @cached(iterable=True, cache_context=True)
  143. async def get_app_service_users_in_room(
  144. self,
  145. room_id: str,
  146. app_service: "ApplicationService",
  147. cache_context: _CacheContext,
  148. ) -> Sequence[str]:
  149. """
  150. Get all users in a room that the appservice controls.
  151. Args:
  152. room_id: The room to check in.
  153. app_service: The application service to check interest/control against
  154. Returns:
  155. List of user IDs that the appservice controls.
  156. """
  157. # We can use `get_local_users_in_room(...)` here because an application service
  158. # can only be interested in local users of the server it's on (ignore any remote
  159. # users that might match the user namespace regex).
  160. local_users_in_room = await self.get_local_users_in_room(
  161. room_id, on_invalidate=cache_context.invalidate
  162. )
  163. return list(filter(app_service.is_interested_in_user, local_users_in_room))
  164. class ApplicationServiceStore(ApplicationServiceWorkerStore):
  165. # This is currently empty due to there not being any AS storage functions
  166. # that can't be run on the workers. Since this may change in future, and
  167. # to keep consistency with the other stores, we keep this empty class for
  168. # now.
  169. pass
  170. class ApplicationServiceTransactionWorkerStore(
  171. ApplicationServiceWorkerStore, EventsWorkerStore
  172. ):
  173. async def get_appservices_by_state(
  174. self, state: ApplicationServiceState
  175. ) -> List[ApplicationService]:
  176. """Get a list of application services based on their state.
  177. Args:
  178. state: The state to filter on.
  179. Returns:
  180. A list of ApplicationServices, which may be empty.
  181. """
  182. results = await self.db_pool.simple_select_list(
  183. "application_services_state", {"state": state.value}, ["as_id"]
  184. )
  185. # NB: This assumes this class is linked with ApplicationServiceStore
  186. as_list = self.get_app_services()
  187. services = []
  188. for res in results:
  189. for service in as_list:
  190. if service.id == res["as_id"]:
  191. services.append(service)
  192. return services
  193. async def get_appservice_state(
  194. self, service: ApplicationService
  195. ) -> Optional[ApplicationServiceState]:
  196. """Get the application service state.
  197. Args:
  198. service: The service whose state to get.
  199. Returns:
  200. An ApplicationServiceState, or None if we have yet to attempt any
  201. transactions to the AS.
  202. """
  203. # if we have created transactions for this AS but not yet attempted to send
  204. # them, we will have a row in the table with state=NULL (recording the stream
  205. # positions we have processed up to).
  206. #
  207. # On the other hand, if we have yet to create any transactions for this AS at
  208. # all, then there will be no row for the AS.
  209. #
  210. # In either case, we return None to indicate "we don't yet know the state of
  211. # this AS".
  212. result = await self.db_pool.simple_select_one_onecol(
  213. "application_services_state",
  214. {"as_id": service.id},
  215. retcol="state",
  216. allow_none=True,
  217. desc="get_appservice_state",
  218. )
  219. if result:
  220. return ApplicationServiceState(result)
  221. return None
  222. async def set_appservice_state(
  223. self, service: ApplicationService, state: ApplicationServiceState
  224. ) -> None:
  225. """Set the application service state.
  226. Args:
  227. service: The service whose state to set.
  228. state: The connectivity state to apply.
  229. """
  230. await self.db_pool.simple_upsert(
  231. "application_services_state", {"as_id": service.id}, {"state": state.value}
  232. )
  233. async def create_appservice_txn(
  234. self,
  235. service: ApplicationService,
  236. events: Sequence[EventBase],
  237. ephemeral: List[JsonMapping],
  238. to_device_messages: List[JsonMapping],
  239. one_time_keys_count: TransactionOneTimeKeysCount,
  240. unused_fallback_keys: TransactionUnusedFallbackKeys,
  241. device_list_summary: DeviceListUpdates,
  242. ) -> AppServiceTransaction:
  243. """Atomically creates a new transaction for this application service
  244. with the given list of events. Ephemeral events are NOT persisted to the
  245. database and are not resent if a transaction is retried.
  246. Args:
  247. service: The service who the transaction is for.
  248. events: A list of persistent events to put in the transaction.
  249. ephemeral: A list of ephemeral events to put in the transaction.
  250. to_device_messages: A list of to-device messages to put in the transaction.
  251. one_time_keys_count: Counts of remaining one-time keys for relevant
  252. appservice devices in the transaction.
  253. unused_fallback_keys: Lists of unused fallback keys for relevant
  254. appservice devices in the transaction.
  255. device_list_summary: The device list summary to include in the transaction.
  256. Returns:
  257. A new transaction.
  258. """
  259. def _create_appservice_txn(txn: LoggingTransaction) -> AppServiceTransaction:
  260. new_txn_id = self._as_txn_seq_gen.get_next_id_txn(txn)
  261. # Insert new txn into txn table
  262. event_ids = json_encoder.encode([e.event_id for e in events])
  263. txn.execute(
  264. "INSERT INTO application_services_txns(as_id, txn_id, event_ids) "
  265. "VALUES(?,?,?)",
  266. (service.id, new_txn_id, event_ids),
  267. )
  268. return AppServiceTransaction(
  269. service=service,
  270. id=new_txn_id,
  271. events=events,
  272. ephemeral=ephemeral,
  273. to_device_messages=to_device_messages,
  274. one_time_keys_count=one_time_keys_count,
  275. unused_fallback_keys=unused_fallback_keys,
  276. device_list_summary=device_list_summary,
  277. )
  278. return await self.db_pool.runInteraction(
  279. "create_appservice_txn", _create_appservice_txn
  280. )
  281. async def complete_appservice_txn(
  282. self, txn_id: int, service: ApplicationService
  283. ) -> None:
  284. """Completes an application service transaction.
  285. Args:
  286. txn_id: The transaction ID being completed.
  287. service: The application service which was sent this transaction.
  288. """
  289. def _complete_appservice_txn(txn: LoggingTransaction) -> None:
  290. # Delete txn
  291. self.db_pool.simple_delete_txn(
  292. txn,
  293. "application_services_txns",
  294. {"txn_id": txn_id, "as_id": service.id},
  295. )
  296. await self.db_pool.runInteraction(
  297. "complete_appservice_txn", _complete_appservice_txn
  298. )
  299. async def get_oldest_unsent_txn(
  300. self, service: ApplicationService
  301. ) -> Optional[AppServiceTransaction]:
  302. """Get the oldest transaction which has not been sent for this service.
  303. Args:
  304. service: The app service to get the oldest txn.
  305. Returns:
  306. An AppServiceTransaction or None.
  307. """
  308. def _get_oldest_unsent_txn(
  309. txn: LoggingTransaction,
  310. ) -> Optional[Dict[str, Any]]:
  311. # Monotonically increasing txn ids, so just select the smallest
  312. # one in the txns table (we delete them when they are sent)
  313. txn.execute(
  314. "SELECT * FROM application_services_txns WHERE as_id=?"
  315. " ORDER BY txn_id ASC LIMIT 1",
  316. (service.id,),
  317. )
  318. rows = self.db_pool.cursor_to_dict(txn)
  319. if not rows:
  320. return None
  321. entry = rows[0]
  322. return entry
  323. entry = await self.db_pool.runInteraction(
  324. "get_oldest_unsent_appservice_txn", _get_oldest_unsent_txn
  325. )
  326. if not entry:
  327. return None
  328. event_ids = db_to_json(entry["event_ids"])
  329. events = await self.get_events_as_list(event_ids)
  330. # TODO: to-device messages, one-time key counts, device list summaries and unused
  331. # fallback keys are not yet populated for catch-up transactions.
  332. # We likely want to populate those for reliability.
  333. return AppServiceTransaction(
  334. service=service,
  335. id=entry["txn_id"],
  336. events=events,
  337. ephemeral=[],
  338. to_device_messages=[],
  339. one_time_keys_count={},
  340. unused_fallback_keys={},
  341. device_list_summary=DeviceListUpdates(),
  342. )
  343. async def get_appservice_last_pos(self) -> int:
  344. """
  345. Get the last stream ordering position for the appservice process.
  346. """
  347. return await self.db_pool.simple_select_one_onecol(
  348. table="appservice_stream_position",
  349. retcol="stream_ordering",
  350. keyvalues={},
  351. desc="get_appservice_last_pos",
  352. )
  353. async def set_appservice_last_pos(self, pos: int) -> None:
  354. """
  355. Set the last stream ordering position for the appservice process.
  356. """
  357. await self.db_pool.simple_update_one(
  358. table="appservice_stream_position",
  359. keyvalues={},
  360. updatevalues={"stream_ordering": pos},
  361. desc="set_appservice_last_pos",
  362. )
  363. async def get_type_stream_id_for_appservice(
  364. self, service: ApplicationService, type: str
  365. ) -> int:
  366. if type not in ("read_receipt", "presence", "to_device", "device_list"):
  367. raise ValueError(
  368. "Expected type to be a valid application stream id type, got %s"
  369. % (type,)
  370. )
  371. def get_type_stream_id_for_appservice_txn(txn: LoggingTransaction) -> int:
  372. stream_id_type = "%s_stream_id" % type
  373. txn.execute(
  374. # We do NOT want to escape `stream_id_type`.
  375. "SELECT %s FROM application_services_state WHERE as_id=?"
  376. % stream_id_type,
  377. (service.id,),
  378. )
  379. last_stream_id = txn.fetchone()
  380. if last_stream_id is None or last_stream_id[0] is None: # no row exists
  381. # Stream tokens always start from 1, to avoid foot guns around `0` being falsey.
  382. return 1
  383. else:
  384. return int(last_stream_id[0])
  385. return await self.db_pool.runInteraction(
  386. "get_type_stream_id_for_appservice", get_type_stream_id_for_appservice_txn
  387. )
  388. async def set_appservice_stream_type_pos(
  389. self, service: ApplicationService, stream_type: str, pos: Optional[int]
  390. ) -> None:
  391. if stream_type not in ("read_receipt", "presence", "to_device", "device_list"):
  392. raise ValueError(
  393. "Expected type to be a valid application stream id type, got %s"
  394. % (stream_type,)
  395. )
  396. # this may be the first time that we're recording any state for this AS, so
  397. # we don't yet know if a row for it exists; hence we have to upsert here.
  398. await self.db_pool.simple_upsert(
  399. table="application_services_state",
  400. keyvalues={"as_id": service.id},
  401. values={f"{stream_type}_stream_id": pos},
  402. desc="set_appservice_stream_type_pos",
  403. )
  404. class ApplicationServiceTransactionStore(ApplicationServiceTransactionWorkerStore):
  405. # This is currently empty due to there not being any AS storage functions
  406. # that can't be run on the workers. Since this may change in future, and
  407. # to keep consistency with the other stores, we keep this empty class for
  408. # now.
  409. pass