registration.py 99 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803
  1. # Copyright 2014-2016 OpenMarket Ltd
  2. # Copyright 2017-2018 New Vector Ltd
  3. # Copyright 2019,2020 The Matrix.org Foundation C.I.C.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import logging
  17. import random
  18. import re
  19. from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Tuple, Union, cast
  20. import attr
  21. from synapse.api.constants import UserTypes
  22. from synapse.api.errors import (
  23. Codes,
  24. NotFoundError,
  25. StoreError,
  26. SynapseError,
  27. ThreepidValidationError,
  28. )
  29. from synapse.config.homeserver import HomeServerConfig
  30. from synapse.metrics.background_process_metrics import wrap_as_background_process
  31. from synapse.storage.database import (
  32. DatabasePool,
  33. LoggingDatabaseConnection,
  34. LoggingTransaction,
  35. )
  36. from synapse.storage.databases.main.cache import CacheInvalidationWorkerStore
  37. from synapse.storage.databases.main.stats import StatsStore
  38. from synapse.storage.types import Cursor
  39. from synapse.storage.util.id_generators import IdGenerator
  40. from synapse.storage.util.sequence import build_sequence_generator
  41. from synapse.types import JsonDict, UserID, UserInfo
  42. from synapse.util.caches.descriptors import cached
  43. if TYPE_CHECKING:
  44. from synapse.server import HomeServer
  45. THIRTY_MINUTES_IN_MS = 30 * 60 * 1000
  46. logger = logging.getLogger(__name__)
  47. class ExternalIDReuseException(Exception):
  48. """Exception if writing an external id for a user fails,
  49. because this external id is given to an other user."""
  50. class LoginTokenExpired(Exception):
  51. """Exception if the login token sent expired"""
  52. class LoginTokenReused(Exception):
  53. """Exception if the login token sent was already used"""
  54. @attr.s(frozen=True, slots=True, auto_attribs=True)
  55. class TokenLookupResult:
  56. """Result of looking up an access token.
  57. Attributes:
  58. user_id: The user that this token authenticates as
  59. is_guest
  60. shadow_banned
  61. token_id: The ID of the access token looked up
  62. device_id: The device associated with the token, if any.
  63. valid_until_ms: The timestamp the token expires, if any.
  64. token_owner: The "owner" of the token. This is either the same as the
  65. user, or a server admin who is logged in as the user.
  66. token_used: True if this token was used at least once in a request.
  67. This field can be out of date since `get_user_by_access_token` is
  68. cached.
  69. """
  70. user_id: str
  71. token_id: int
  72. is_guest: bool = False
  73. shadow_banned: bool = False
  74. device_id: Optional[str] = None
  75. valid_until_ms: Optional[int] = None
  76. token_owner: str = attr.ib()
  77. token_used: bool = False
  78. # Make the token owner default to the user ID, which is the common case.
  79. @token_owner.default
  80. def _default_token_owner(self) -> str:
  81. return self.user_id
  82. @attr.s(auto_attribs=True, frozen=True, slots=True)
  83. class RefreshTokenLookupResult:
  84. """Result of looking up a refresh token."""
  85. user_id: str
  86. """The user this token belongs to."""
  87. device_id: str
  88. """The device associated with this refresh token."""
  89. token_id: int
  90. """The ID of this refresh token."""
  91. next_token_id: Optional[int]
  92. """The ID of the refresh token which replaced this one."""
  93. has_next_refresh_token_been_refreshed: bool
  94. """True if the next refresh token was used for another refresh."""
  95. has_next_access_token_been_used: bool
  96. """True if the next access token was already used at least once."""
  97. expiry_ts: Optional[int]
  98. """The time at which the refresh token expires and can not be used.
  99. If None, the refresh token doesn't expire."""
  100. ultimate_session_expiry_ts: Optional[int]
  101. """The time at which the session comes to an end and can no longer be
  102. refreshed.
  103. If None, the session can be refreshed indefinitely."""
  104. @attr.s(auto_attribs=True, frozen=True, slots=True)
  105. class LoginTokenLookupResult:
  106. """Result of looking up a login token."""
  107. user_id: str
  108. """The user this token belongs to."""
  109. auth_provider_id: Optional[str]
  110. """The SSO Identity Provider that the user authenticated with, to get this token."""
  111. auth_provider_session_id: Optional[str]
  112. """The session ID advertised by the SSO Identity Provider."""
  113. class RegistrationWorkerStore(CacheInvalidationWorkerStore):
  114. def __init__(
  115. self,
  116. database: DatabasePool,
  117. db_conn: LoggingDatabaseConnection,
  118. hs: "HomeServer",
  119. ):
  120. super().__init__(database, db_conn, hs)
  121. self.config: HomeServerConfig = hs.config
  122. # Note: we don't check this sequence for consistency as we'd have to
  123. # call `find_max_generated_user_id_localpart` each time, which is
  124. # expensive if there are many entries.
  125. self._user_id_seq = build_sequence_generator(
  126. db_conn,
  127. database.engine,
  128. find_max_generated_user_id_localpart,
  129. "user_id_seq",
  130. table=None,
  131. id_column=None,
  132. )
  133. self._account_validity_enabled = (
  134. hs.config.account_validity.account_validity_enabled
  135. )
  136. self._account_validity_period = None
  137. self._account_validity_startup_job_max_delta = None
  138. if self._account_validity_enabled:
  139. self._account_validity_period = (
  140. hs.config.account_validity.account_validity_period
  141. )
  142. self._account_validity_startup_job_max_delta = (
  143. hs.config.account_validity.account_validity_startup_job_max_delta
  144. )
  145. if hs.config.worker.run_background_tasks:
  146. self._clock.call_later(
  147. 0.0,
  148. self._set_expiration_date_when_missing,
  149. )
  150. # Create a background job for culling expired 3PID validity tokens
  151. if hs.config.worker.run_background_tasks:
  152. self._clock.looping_call(
  153. self.cull_expired_threepid_validation_tokens, THIRTY_MINUTES_IN_MS
  154. )
  155. @cached()
  156. async def get_user_by_id(self, user_id: str) -> Optional[Mapping[str, Any]]:
  157. """Deprecated: use get_userinfo_by_id instead"""
  158. def get_user_by_id_txn(txn: LoggingTransaction) -> Optional[Dict[str, Any]]:
  159. # We could technically use simple_select_one here, but it would not perform
  160. # the COALESCEs (unless hacked into the column names), which could yield
  161. # confusing results.
  162. txn.execute(
  163. """
  164. SELECT
  165. name, password_hash, is_guest, admin, consent_version, consent_ts,
  166. consent_server_notice_sent, appservice_id, creation_ts, user_type,
  167. deactivated, COALESCE(shadow_banned, FALSE) AS shadow_banned,
  168. COALESCE(approved, TRUE) AS approved
  169. FROM users
  170. WHERE name = ?
  171. """,
  172. (user_id,),
  173. )
  174. rows = self.db_pool.cursor_to_dict(txn)
  175. if len(rows) == 0:
  176. return None
  177. return rows[0]
  178. row = await self.db_pool.runInteraction(
  179. desc="get_user_by_id",
  180. func=get_user_by_id_txn,
  181. )
  182. if row is not None:
  183. # If we're using SQLite our boolean values will be integers. Because we
  184. # present some of this data as is to e.g. server admins via REST APIs, we
  185. # want to make sure we're returning the right type of data.
  186. # Note: when adding a column name to this list, be wary of NULLable columns,
  187. # since NULL values will be turned into False.
  188. boolean_columns = ["admin", "deactivated", "shadow_banned", "approved"]
  189. for column in boolean_columns:
  190. if not isinstance(row[column], bool):
  191. row[column] = bool(row[column])
  192. return row
  193. async def get_userinfo_by_id(self, user_id: str) -> Optional[UserInfo]:
  194. """Get a UserInfo object for a user by user ID.
  195. Note! Currently uses the cache of `get_user_by_id`. Once that deprecated method is removed,
  196. this method should be cached.
  197. Args:
  198. user_id: The user to fetch user info for.
  199. Returns:
  200. `UserInfo` object if user found, otherwise `None`.
  201. """
  202. user_data = await self.get_user_by_id(user_id)
  203. if not user_data:
  204. return None
  205. return UserInfo(
  206. appservice_id=user_data["appservice_id"],
  207. consent_server_notice_sent=user_data["consent_server_notice_sent"],
  208. consent_version=user_data["consent_version"],
  209. creation_ts=user_data["creation_ts"],
  210. is_admin=bool(user_data["admin"]),
  211. is_deactivated=bool(user_data["deactivated"]),
  212. is_guest=bool(user_data["is_guest"]),
  213. is_shadow_banned=bool(user_data["shadow_banned"]),
  214. user_id=UserID.from_string(user_data["name"]),
  215. user_type=user_data["user_type"],
  216. )
  217. async def is_trial_user(self, user_id: str) -> bool:
  218. """Checks if user is in the "trial" period, i.e. within the first
  219. N days of registration defined by `mau_trial_days` config or the
  220. `mau_appservice_trial_days` config.
  221. Args:
  222. user_id: The user to check for trial status.
  223. """
  224. info = await self.get_user_by_id(user_id)
  225. if not info:
  226. return False
  227. now = self._clock.time_msec()
  228. days = self.config.server.mau_appservice_trial_days.get(
  229. info["appservice_id"], self.config.server.mau_trial_days
  230. )
  231. trial_duration_ms = days * 24 * 60 * 60 * 1000
  232. is_trial = (now - info["creation_ts"] * 1000) < trial_duration_ms
  233. return is_trial
  234. @cached()
  235. async def get_user_by_access_token(self, token: str) -> Optional[TokenLookupResult]:
  236. """Get a user from the given access token.
  237. Args:
  238. token: The access token of a user.
  239. Returns:
  240. None, if the token did not match, otherwise a `TokenLookupResult`
  241. """
  242. return await self.db_pool.runInteraction(
  243. "get_user_by_access_token", self._query_for_auth, token
  244. )
  245. @cached()
  246. async def get_expiration_ts_for_user(self, user_id: str) -> Optional[int]:
  247. """Get the expiration timestamp for the account bearing a given user ID.
  248. Args:
  249. user_id: The ID of the user.
  250. Returns:
  251. None, if the account has no expiration timestamp, otherwise int
  252. representation of the timestamp (as a number of milliseconds since epoch).
  253. """
  254. return await self.db_pool.simple_select_one_onecol(
  255. table="account_validity",
  256. keyvalues={"user_id": user_id},
  257. retcol="expiration_ts_ms",
  258. allow_none=True,
  259. desc="get_expiration_ts_for_user",
  260. )
  261. async def is_account_expired(self, user_id: str, current_ts: int) -> bool:
  262. """
  263. Returns whether an user account is expired.
  264. Args:
  265. user_id: The user's ID
  266. current_ts: The current timestamp
  267. Returns:
  268. Whether the user account has expired
  269. """
  270. expiration_ts = await self.get_expiration_ts_for_user(user_id)
  271. return expiration_ts is not None and current_ts >= expiration_ts
  272. async def set_account_validity_for_user(
  273. self,
  274. user_id: str,
  275. expiration_ts: int,
  276. email_sent: bool,
  277. renewal_token: Optional[str] = None,
  278. token_used_ts: Optional[int] = None,
  279. ) -> None:
  280. """Updates the account validity properties of the given account, with the
  281. given values.
  282. Args:
  283. user_id: ID of the account to update properties for.
  284. expiration_ts: New expiration date, as a timestamp in milliseconds
  285. since epoch.
  286. email_sent: True means a renewal email has been sent for this account
  287. and there's no need to send another one for the current validity
  288. period.
  289. renewal_token: Renewal token the user can use to extend the validity
  290. of their account. Defaults to no token.
  291. token_used_ts: A timestamp of when the current token was used to renew
  292. the account.
  293. """
  294. def set_account_validity_for_user_txn(txn: LoggingTransaction) -> None:
  295. self.db_pool.simple_update_txn(
  296. txn=txn,
  297. table="account_validity",
  298. keyvalues={"user_id": user_id},
  299. updatevalues={
  300. "expiration_ts_ms": expiration_ts,
  301. "email_sent": email_sent,
  302. "renewal_token": renewal_token,
  303. "token_used_ts_ms": token_used_ts,
  304. },
  305. )
  306. self._invalidate_cache_and_stream(
  307. txn, self.get_expiration_ts_for_user, (user_id,)
  308. )
  309. await self.db_pool.runInteraction(
  310. "set_account_validity_for_user", set_account_validity_for_user_txn
  311. )
  312. async def set_renewal_token_for_user(
  313. self, user_id: str, renewal_token: str
  314. ) -> None:
  315. """Defines a renewal token for a given user, and clears the token_used timestamp.
  316. Args:
  317. user_id: ID of the user to set the renewal token for.
  318. renewal_token: Random unique string that will be used to renew the
  319. user's account.
  320. Raises:
  321. StoreError: The provided token is already set for another user.
  322. """
  323. await self.db_pool.simple_update_one(
  324. table="account_validity",
  325. keyvalues={"user_id": user_id},
  326. updatevalues={"renewal_token": renewal_token, "token_used_ts_ms": None},
  327. desc="set_renewal_token_for_user",
  328. )
  329. async def get_user_from_renewal_token(
  330. self, renewal_token: str
  331. ) -> Tuple[str, int, Optional[int]]:
  332. """Get a user ID and renewal status from a renewal token.
  333. Args:
  334. renewal_token: The renewal token to perform the lookup with.
  335. Returns:
  336. A tuple of containing the following values:
  337. * The ID of a user to which the token belongs.
  338. * An int representing the user's expiry timestamp as milliseconds since the
  339. epoch, or 0 if the token was invalid.
  340. * An optional int representing the timestamp of when the user renewed their
  341. account timestamp as milliseconds since the epoch. None if the account
  342. has not been renewed using the current token yet.
  343. """
  344. ret_dict = await self.db_pool.simple_select_one(
  345. table="account_validity",
  346. keyvalues={"renewal_token": renewal_token},
  347. retcols=["user_id", "expiration_ts_ms", "token_used_ts_ms"],
  348. desc="get_user_from_renewal_token",
  349. )
  350. return (
  351. ret_dict["user_id"],
  352. ret_dict["expiration_ts_ms"],
  353. ret_dict["token_used_ts_ms"],
  354. )
  355. async def get_renewal_token_for_user(self, user_id: str) -> str:
  356. """Get the renewal token associated with a given user ID.
  357. Args:
  358. user_id: The user ID to lookup a token for.
  359. Returns:
  360. The renewal token associated with this user ID.
  361. """
  362. return await self.db_pool.simple_select_one_onecol(
  363. table="account_validity",
  364. keyvalues={"user_id": user_id},
  365. retcol="renewal_token",
  366. desc="get_renewal_token_for_user",
  367. )
  368. async def get_users_expiring_soon(self) -> List[Tuple[str, int]]:
  369. """Selects users whose account will expire in the [now, now + renew_at] time
  370. window (see configuration for account_validity for information on what renew_at
  371. refers to).
  372. Returns:
  373. A list of tuples, each with a user ID and expiration time (in milliseconds).
  374. """
  375. def select_users_txn(
  376. txn: LoggingTransaction, now_ms: int, renew_at: int
  377. ) -> List[Tuple[str, int]]:
  378. sql = (
  379. "SELECT user_id, expiration_ts_ms FROM account_validity"
  380. " WHERE email_sent = ? AND (expiration_ts_ms - ?) <= ?"
  381. )
  382. values = [False, now_ms, renew_at]
  383. txn.execute(sql, values)
  384. return cast(List[Tuple[str, int]], txn.fetchall())
  385. return await self.db_pool.runInteraction(
  386. "get_users_expiring_soon",
  387. select_users_txn,
  388. self._clock.time_msec(),
  389. self.config.account_validity.account_validity_renew_at,
  390. )
  391. async def set_renewal_mail_status(self, user_id: str, email_sent: bool) -> None:
  392. """Sets or unsets the flag that indicates whether a renewal email has been sent
  393. to the user (and the user hasn't renewed their account yet).
  394. Args:
  395. user_id: ID of the user to set/unset the flag for.
  396. email_sent: Flag which indicates whether a renewal email has been sent
  397. to this user.
  398. """
  399. await self.db_pool.simple_update_one(
  400. table="account_validity",
  401. keyvalues={"user_id": user_id},
  402. updatevalues={"email_sent": email_sent},
  403. desc="set_renewal_mail_status",
  404. )
  405. async def delete_account_validity_for_user(self, user_id: str) -> None:
  406. """Deletes the entry for the given user in the account validity table, removing
  407. their expiration date and renewal token.
  408. Args:
  409. user_id: ID of the user to remove from the account validity table.
  410. """
  411. await self.db_pool.simple_delete_one(
  412. table="account_validity",
  413. keyvalues={"user_id": user_id},
  414. desc="delete_account_validity_for_user",
  415. )
  416. async def is_server_admin(self, user: UserID) -> bool:
  417. """Determines if a user is an admin of this homeserver.
  418. Args:
  419. user: user ID of the user to test
  420. Returns:
  421. true iff the user is a server admin, false otherwise.
  422. """
  423. res = await self.db_pool.simple_select_one_onecol(
  424. table="users",
  425. keyvalues={"name": user.to_string()},
  426. retcol="admin",
  427. allow_none=True,
  428. desc="is_server_admin",
  429. )
  430. return bool(res) if res else False
  431. async def set_server_admin(self, user: UserID, admin: bool) -> None:
  432. """Sets whether a user is an admin of this homeserver.
  433. Args:
  434. user: user ID of the user to test
  435. admin: true iff the user is to be a server admin, false otherwise.
  436. """
  437. def set_server_admin_txn(txn: LoggingTransaction) -> None:
  438. self.db_pool.simple_update_one_txn(
  439. txn, "users", {"name": user.to_string()}, {"admin": 1 if admin else 0}
  440. )
  441. self._invalidate_cache_and_stream(
  442. txn, self.get_user_by_id, (user.to_string(),)
  443. )
  444. await self.db_pool.runInteraction("set_server_admin", set_server_admin_txn)
  445. async def set_shadow_banned(self, user: UserID, shadow_banned: bool) -> None:
  446. """Sets whether a user shadow-banned.
  447. Args:
  448. user: user ID of the user to test
  449. shadow_banned: true iff the user is to be shadow-banned, false otherwise.
  450. """
  451. def set_shadow_banned_txn(txn: LoggingTransaction) -> None:
  452. user_id = user.to_string()
  453. self.db_pool.simple_update_one_txn(
  454. txn,
  455. table="users",
  456. keyvalues={"name": user_id},
  457. updatevalues={"shadow_banned": shadow_banned},
  458. )
  459. # In order for this to apply immediately, clear the cache for this user.
  460. tokens = self.db_pool.simple_select_onecol_txn(
  461. txn,
  462. table="access_tokens",
  463. keyvalues={"user_id": user_id},
  464. retcol="token",
  465. )
  466. for token in tokens:
  467. self._invalidate_cache_and_stream(
  468. txn, self.get_user_by_access_token, (token,)
  469. )
  470. self._invalidate_cache_and_stream(txn, self.get_user_by_id, (user_id,))
  471. await self.db_pool.runInteraction("set_shadow_banned", set_shadow_banned_txn)
  472. async def set_user_type(self, user: UserID, user_type: Optional[UserTypes]) -> None:
  473. """Sets the user type.
  474. Args:
  475. user: user ID of the user.
  476. user_type: type of the user or None for a user without a type.
  477. """
  478. def set_user_type_txn(txn: LoggingTransaction) -> None:
  479. self.db_pool.simple_update_one_txn(
  480. txn, "users", {"name": user.to_string()}, {"user_type": user_type}
  481. )
  482. self._invalidate_cache_and_stream(
  483. txn, self.get_user_by_id, (user.to_string(),)
  484. )
  485. await self.db_pool.runInteraction("set_user_type", set_user_type_txn)
  486. def _query_for_auth(
  487. self, txn: LoggingTransaction, token: str
  488. ) -> Optional[TokenLookupResult]:
  489. sql = """
  490. SELECT users.name as user_id,
  491. users.is_guest,
  492. users.shadow_banned,
  493. access_tokens.id as token_id,
  494. access_tokens.device_id,
  495. access_tokens.valid_until_ms,
  496. access_tokens.user_id as token_owner,
  497. access_tokens.used as token_used
  498. FROM users
  499. INNER JOIN access_tokens on users.name = COALESCE(puppets_user_id, access_tokens.user_id)
  500. WHERE token = ?
  501. """
  502. txn.execute(sql, (token,))
  503. rows = self.db_pool.cursor_to_dict(txn)
  504. if rows:
  505. row = rows[0]
  506. # This field is nullable, ensure it comes out as a boolean
  507. if row["token_used"] is None:
  508. row["token_used"] = False
  509. return TokenLookupResult(**row)
  510. return None
  511. @cached()
  512. async def is_real_user(self, user_id: str) -> bool:
  513. """Determines if the user is a real user, ie does not have a 'user_type'.
  514. Args:
  515. user_id: user id to test
  516. Returns:
  517. True if user 'user_type' is null or empty string
  518. """
  519. return await self.db_pool.runInteraction(
  520. "is_real_user", self.is_real_user_txn, user_id
  521. )
  522. @cached()
  523. async def is_support_user(self, user_id: str) -> bool:
  524. """Determines if the user is of type UserTypes.SUPPORT
  525. Args:
  526. user_id: user id to test
  527. Returns:
  528. True if user is of type UserTypes.SUPPORT
  529. """
  530. return await self.db_pool.runInteraction(
  531. "is_support_user", self.is_support_user_txn, user_id
  532. )
  533. def is_real_user_txn(self, txn: LoggingTransaction, user_id: str) -> bool:
  534. res = self.db_pool.simple_select_one_onecol_txn(
  535. txn=txn,
  536. table="users",
  537. keyvalues={"name": user_id},
  538. retcol="user_type",
  539. allow_none=True,
  540. )
  541. return res is None
  542. def is_support_user_txn(self, txn: LoggingTransaction, user_id: str) -> bool:
  543. res = self.db_pool.simple_select_one_onecol_txn(
  544. txn=txn,
  545. table="users",
  546. keyvalues={"name": user_id},
  547. retcol="user_type",
  548. allow_none=True,
  549. )
  550. return True if res == UserTypes.SUPPORT else False
  551. async def get_users_by_id_case_insensitive(self, user_id: str) -> Dict[str, str]:
  552. """Gets users that match user_id case insensitively.
  553. Returns:
  554. A mapping of user_id -> password_hash.
  555. """
  556. def f(txn: LoggingTransaction) -> Dict[str, str]:
  557. sql = "SELECT name, password_hash FROM users WHERE lower(name) = lower(?)"
  558. txn.execute(sql, (user_id,))
  559. result = cast(List[Tuple[str, str]], txn.fetchall())
  560. return dict(result)
  561. return await self.db_pool.runInteraction("get_users_by_id_case_insensitive", f)
  562. async def record_user_external_id(
  563. self, auth_provider: str, external_id: str, user_id: str
  564. ) -> None:
  565. """Record a mapping from an external user id to a mxid
  566. See notes in _record_user_external_id_txn about what constitutes valid data.
  567. Args:
  568. auth_provider: identifier for the remote auth provider
  569. external_id: id on that system
  570. user_id: complete mxid that it is mapped to
  571. Raises:
  572. ExternalIDReuseException if the new external_id could not be mapped.
  573. """
  574. try:
  575. await self.db_pool.runInteraction(
  576. "record_user_external_id",
  577. self._record_user_external_id_txn,
  578. auth_provider,
  579. external_id,
  580. user_id,
  581. )
  582. except self.database_engine.module.IntegrityError:
  583. raise ExternalIDReuseException()
  584. def _record_user_external_id_txn(
  585. self,
  586. txn: LoggingTransaction,
  587. auth_provider: str,
  588. external_id: str,
  589. user_id: str,
  590. ) -> None:
  591. """
  592. Record a mapping from an external user id to a mxid.
  593. Note that the auth provider IDs (and the external IDs) are not validated
  594. against configured IdPs as Synapse does not know its relationship to
  595. external systems. For example, it might be useful to pre-configure users
  596. before enabling a new IdP or an IdP might be temporarily offline, but
  597. still valid.
  598. Args:
  599. txn: The database transaction.
  600. auth_provider: identifier for the remote auth provider
  601. external_id: id on that system
  602. user_id: complete mxid that it is mapped to
  603. """
  604. self.db_pool.simple_insert_txn(
  605. txn,
  606. table="user_external_ids",
  607. values={
  608. "auth_provider": auth_provider,
  609. "external_id": external_id,
  610. "user_id": user_id,
  611. },
  612. )
  613. async def remove_user_external_id(
  614. self, auth_provider: str, external_id: str, user_id: str
  615. ) -> None:
  616. """Remove a mapping from an external user id to a mxid
  617. If the mapping is not found, this method does nothing.
  618. Args:
  619. auth_provider: identifier for the remote auth provider
  620. external_id: id on that system
  621. user_id: complete mxid that it is mapped to
  622. """
  623. await self.db_pool.simple_delete(
  624. table="user_external_ids",
  625. keyvalues={
  626. "auth_provider": auth_provider,
  627. "external_id": external_id,
  628. "user_id": user_id,
  629. },
  630. desc="remove_user_external_id",
  631. )
  632. async def replace_user_external_id(
  633. self,
  634. record_external_ids: List[Tuple[str, str]],
  635. user_id: str,
  636. ) -> None:
  637. """Replace mappings from external user ids to a mxid in a single transaction.
  638. All mappings are deleted and the new ones are created.
  639. See notes in _record_user_external_id_txn about what constitutes valid data.
  640. Args:
  641. record_external_ids:
  642. List with tuple of auth_provider and external_id to record
  643. user_id: complete mxid that it is mapped to
  644. Raises:
  645. ExternalIDReuseException if the new external_id could not be mapped.
  646. """
  647. def _remove_user_external_ids_txn(
  648. txn: LoggingTransaction,
  649. user_id: str,
  650. ) -> None:
  651. """Remove all mappings from external user ids to a mxid
  652. If these mappings are not found, this method does nothing.
  653. Args:
  654. user_id: complete mxid that it is mapped to
  655. """
  656. self.db_pool.simple_delete_txn(
  657. txn,
  658. table="user_external_ids",
  659. keyvalues={"user_id": user_id},
  660. )
  661. def _replace_user_external_id_txn(
  662. txn: LoggingTransaction,
  663. ) -> None:
  664. _remove_user_external_ids_txn(txn, user_id)
  665. for auth_provider, external_id in record_external_ids:
  666. self._record_user_external_id_txn(
  667. txn,
  668. auth_provider,
  669. external_id,
  670. user_id,
  671. )
  672. try:
  673. await self.db_pool.runInteraction(
  674. "replace_user_external_id",
  675. _replace_user_external_id_txn,
  676. )
  677. except self.database_engine.module.IntegrityError:
  678. raise ExternalIDReuseException()
  679. async def get_user_by_external_id(
  680. self, auth_provider: str, external_id: str
  681. ) -> Optional[str]:
  682. """Look up a user by their external auth id
  683. Args:
  684. auth_provider: identifier for the remote auth provider
  685. external_id: id on that system
  686. Returns:
  687. the mxid of the user, or None if they are not known
  688. """
  689. return await self.db_pool.simple_select_one_onecol(
  690. table="user_external_ids",
  691. keyvalues={"auth_provider": auth_provider, "external_id": external_id},
  692. retcol="user_id",
  693. allow_none=True,
  694. desc="get_user_by_external_id",
  695. )
  696. async def get_external_ids_by_user(self, mxid: str) -> List[Tuple[str, str]]:
  697. """Look up external ids for the given user
  698. Args:
  699. mxid: the MXID to be looked up
  700. Returns:
  701. Tuples of (auth_provider, external_id)
  702. """
  703. res = await self.db_pool.simple_select_list(
  704. table="user_external_ids",
  705. keyvalues={"user_id": mxid},
  706. retcols=("auth_provider", "external_id"),
  707. desc="get_external_ids_by_user",
  708. )
  709. return [(r["auth_provider"], r["external_id"]) for r in res]
  710. async def count_all_users(self) -> int:
  711. """Counts all users registered on the homeserver."""
  712. def _count_users(txn: LoggingTransaction) -> int:
  713. txn.execute("SELECT COUNT(*) AS users FROM users")
  714. rows = self.db_pool.cursor_to_dict(txn)
  715. if rows:
  716. return rows[0]["users"]
  717. return 0
  718. return await self.db_pool.runInteraction("count_users", _count_users)
  719. async def count_daily_user_type(self) -> Dict[str, int]:
  720. """
  721. Counts 1) native non guest users
  722. 2) native guests users
  723. 3) bridged users
  724. who registered on the homeserver in the past 24 hours
  725. """
  726. def _count_daily_user_type(txn: LoggingTransaction) -> Dict[str, int]:
  727. yesterday = int(self._clock.time()) - (60 * 60 * 24)
  728. sql = """
  729. SELECT user_type, COUNT(*) AS count FROM (
  730. SELECT
  731. CASE
  732. WHEN is_guest=0 AND appservice_id IS NULL THEN 'native'
  733. WHEN is_guest=1 AND appservice_id IS NULL THEN 'guest'
  734. WHEN is_guest=0 AND appservice_id IS NOT NULL THEN 'bridged'
  735. END AS user_type
  736. FROM users
  737. WHERE creation_ts > ?
  738. ) AS t GROUP BY user_type
  739. """
  740. results = {"native": 0, "guest": 0, "bridged": 0}
  741. txn.execute(sql, (yesterday,))
  742. for row in txn:
  743. results[row[0]] = row[1]
  744. return results
  745. return await self.db_pool.runInteraction(
  746. "count_daily_user_type", _count_daily_user_type
  747. )
  748. async def count_nonbridged_users(self) -> int:
  749. def _count_users(txn: LoggingTransaction) -> int:
  750. txn.execute(
  751. """
  752. SELECT COUNT(*) FROM users
  753. WHERE appservice_id IS NULL
  754. """
  755. )
  756. (count,) = cast(Tuple[int], txn.fetchone())
  757. return count
  758. return await self.db_pool.runInteraction("count_users", _count_users)
  759. async def count_real_users(self) -> int:
  760. """Counts all users without a special user_type registered on the homeserver."""
  761. def _count_users(txn: LoggingTransaction) -> int:
  762. txn.execute("SELECT COUNT(*) AS users FROM users where user_type is null")
  763. rows = self.db_pool.cursor_to_dict(txn)
  764. if rows:
  765. return rows[0]["users"]
  766. return 0
  767. return await self.db_pool.runInteraction("count_real_users", _count_users)
  768. async def generate_user_id(self) -> str:
  769. """Generate a suitable localpart for a guest user
  770. Returns: a (hopefully) free localpart
  771. """
  772. next_id = await self.db_pool.runInteraction(
  773. "generate_user_id", self._user_id_seq.get_next_id_txn
  774. )
  775. return str(next_id)
  776. async def get_user_id_by_threepid(self, medium: str, address: str) -> Optional[str]:
  777. """Returns user id from threepid
  778. Args:
  779. medium: threepid medium e.g. email
  780. address: threepid address e.g. me@example.com. This must already be
  781. in canonical form.
  782. Returns:
  783. The user ID or None if no user id/threepid mapping exists
  784. """
  785. user_id = await self.db_pool.runInteraction(
  786. "get_user_id_by_threepid", self.get_user_id_by_threepid_txn, medium, address
  787. )
  788. return user_id
  789. def get_user_id_by_threepid_txn(
  790. self, txn: LoggingTransaction, medium: str, address: str
  791. ) -> Optional[str]:
  792. """Returns user id from threepid
  793. Args:
  794. txn:
  795. medium: threepid medium e.g. email
  796. address: threepid address e.g. me@example.com
  797. Returns:
  798. user id, or None if no user id/threepid mapping exists
  799. """
  800. ret = self.db_pool.simple_select_one_txn(
  801. txn,
  802. "user_threepids",
  803. {"medium": medium, "address": address},
  804. ["user_id"],
  805. True,
  806. )
  807. if ret:
  808. return ret["user_id"]
  809. return None
  810. async def user_add_threepid(
  811. self,
  812. user_id: str,
  813. medium: str,
  814. address: str,
  815. validated_at: int,
  816. added_at: int,
  817. ) -> None:
  818. await self.db_pool.simple_upsert(
  819. "user_threepids",
  820. {"medium": medium, "address": address},
  821. {"user_id": user_id, "validated_at": validated_at, "added_at": added_at},
  822. )
  823. async def user_get_threepids(self, user_id: str) -> List[Dict[str, Any]]:
  824. return await self.db_pool.simple_select_list(
  825. "user_threepids",
  826. {"user_id": user_id},
  827. ["medium", "address", "validated_at", "added_at"],
  828. "user_get_threepids",
  829. )
  830. async def user_delete_threepid(
  831. self, user_id: str, medium: str, address: str
  832. ) -> None:
  833. await self.db_pool.simple_delete(
  834. "user_threepids",
  835. keyvalues={"user_id": user_id, "medium": medium, "address": address},
  836. desc="user_delete_threepid",
  837. )
  838. async def add_user_bound_threepid(
  839. self, user_id: str, medium: str, address: str, id_server: str
  840. ) -> None:
  841. """The server proxied a bind request to the given identity server on
  842. behalf of the given user. We need to remember this in case the user
  843. asks us to unbind the threepid.
  844. Args:
  845. user_id
  846. medium
  847. address
  848. id_server
  849. """
  850. # We need to use an upsert, in case they user had already bound the
  851. # threepid
  852. await self.db_pool.simple_upsert(
  853. table="user_threepid_id_server",
  854. keyvalues={
  855. "user_id": user_id,
  856. "medium": medium,
  857. "address": address,
  858. "id_server": id_server,
  859. },
  860. values={},
  861. insertion_values={},
  862. desc="add_user_bound_threepid",
  863. )
  864. async def user_get_bound_threepids(self, user_id: str) -> List[Dict[str, Any]]:
  865. """Get the threepids that a user has bound to an identity server through the homeserver
  866. The homeserver remembers where binds to an identity server occurred. Using this
  867. method can retrieve those threepids.
  868. Args:
  869. user_id: The ID of the user to retrieve threepids for
  870. Returns:
  871. List of dictionaries containing the following keys:
  872. medium (str): The medium of the threepid (e.g "email")
  873. address (str): The address of the threepid (e.g "bob@example.com")
  874. """
  875. return await self.db_pool.simple_select_list(
  876. table="user_threepid_id_server",
  877. keyvalues={"user_id": user_id},
  878. retcols=["medium", "address"],
  879. desc="user_get_bound_threepids",
  880. )
  881. async def remove_user_bound_threepid(
  882. self, user_id: str, medium: str, address: str, id_server: str
  883. ) -> None:
  884. """The server proxied an unbind request to the given identity server on
  885. behalf of the given user, so we remove the mapping of threepid to
  886. identity server.
  887. Args:
  888. user_id
  889. medium
  890. address
  891. id_server
  892. """
  893. await self.db_pool.simple_delete(
  894. table="user_threepid_id_server",
  895. keyvalues={
  896. "user_id": user_id,
  897. "medium": medium,
  898. "address": address,
  899. "id_server": id_server,
  900. },
  901. desc="remove_user_bound_threepid",
  902. )
  903. async def get_id_servers_user_bound(
  904. self, user_id: str, medium: str, address: str
  905. ) -> List[str]:
  906. """Get the list of identity servers that the server proxied bind
  907. requests to for given user and threepid
  908. Args:
  909. user_id: The user to query for identity servers.
  910. medium: The medium to query for identity servers.
  911. address: The address to query for identity servers.
  912. Returns:
  913. A list of identity servers
  914. """
  915. return await self.db_pool.simple_select_onecol(
  916. table="user_threepid_id_server",
  917. keyvalues={"user_id": user_id, "medium": medium, "address": address},
  918. retcol="id_server",
  919. desc="get_id_servers_user_bound",
  920. )
  921. @cached()
  922. async def get_user_deactivated_status(self, user_id: str) -> bool:
  923. """Retrieve the value for the `deactivated` property for the provided user.
  924. Args:
  925. user_id: The ID of the user to retrieve the status for.
  926. Returns:
  927. True if the user was deactivated, false if the user is still active.
  928. """
  929. res = await self.db_pool.simple_select_one_onecol(
  930. table="users",
  931. keyvalues={"name": user_id},
  932. retcol="deactivated",
  933. desc="get_user_deactivated_status",
  934. )
  935. # Convert the integer into a boolean.
  936. return res == 1
  937. async def get_threepid_validation_session(
  938. self,
  939. medium: Optional[str],
  940. client_secret: str,
  941. address: Optional[str] = None,
  942. sid: Optional[str] = None,
  943. validated: Optional[bool] = True,
  944. ) -> Optional[Dict[str, Any]]:
  945. """Gets a session_id and last_send_attempt (if available) for a
  946. combination of validation metadata
  947. Args:
  948. medium: The medium of the 3PID
  949. client_secret: A unique string provided by the client to help identify this
  950. validation attempt
  951. address: The address of the 3PID
  952. sid: The ID of the validation session
  953. validated: Whether sessions should be filtered by
  954. whether they have been validated already or not. None to
  955. perform no filtering
  956. Returns:
  957. A dict containing the following:
  958. * address - address of the 3pid
  959. * medium - medium of the 3pid
  960. * client_secret - a secret provided by the client for this validation session
  961. * session_id - ID of the validation session
  962. * send_attempt - a number serving to dedupe send attempts for this session
  963. * validated_at - timestamp of when this session was validated if so
  964. Otherwise None if a validation session is not found
  965. """
  966. if not client_secret:
  967. raise SynapseError(
  968. 400, "Missing parameter: client_secret", errcode=Codes.MISSING_PARAM
  969. )
  970. keyvalues = {"client_secret": client_secret}
  971. if medium:
  972. keyvalues["medium"] = medium
  973. if address:
  974. keyvalues["address"] = address
  975. if sid:
  976. keyvalues["session_id"] = sid
  977. assert address or sid
  978. def get_threepid_validation_session_txn(
  979. txn: LoggingTransaction,
  980. ) -> Optional[Dict[str, Any]]:
  981. sql = """
  982. SELECT address, session_id, medium, client_secret,
  983. last_send_attempt, validated_at
  984. FROM threepid_validation_session WHERE %s
  985. """ % (
  986. " AND ".join("%s = ?" % k for k in keyvalues.keys()),
  987. )
  988. if validated is not None:
  989. sql += " AND validated_at IS " + ("NOT NULL" if validated else "NULL")
  990. sql += " LIMIT 1"
  991. txn.execute(sql, list(keyvalues.values()))
  992. rows = self.db_pool.cursor_to_dict(txn)
  993. if not rows:
  994. return None
  995. return rows[0]
  996. return await self.db_pool.runInteraction(
  997. "get_threepid_validation_session", get_threepid_validation_session_txn
  998. )
  999. async def delete_threepid_session(self, session_id: str) -> None:
  1000. """Removes a threepid validation session from the database. This can
  1001. be done after validation has been performed and whatever action was
  1002. waiting on it has been carried out
  1003. Args:
  1004. session_id: The ID of the session to delete
  1005. """
  1006. def delete_threepid_session_txn(txn: LoggingTransaction) -> None:
  1007. self.db_pool.simple_delete_txn(
  1008. txn,
  1009. table="threepid_validation_token",
  1010. keyvalues={"session_id": session_id},
  1011. )
  1012. self.db_pool.simple_delete_txn(
  1013. txn,
  1014. table="threepid_validation_session",
  1015. keyvalues={"session_id": session_id},
  1016. )
  1017. await self.db_pool.runInteraction(
  1018. "delete_threepid_session", delete_threepid_session_txn
  1019. )
  1020. @wrap_as_background_process("cull_expired_threepid_validation_tokens")
  1021. async def cull_expired_threepid_validation_tokens(self) -> None:
  1022. """Remove threepid validation tokens with expiry dates that have passed"""
  1023. def cull_expired_threepid_validation_tokens_txn(
  1024. txn: LoggingTransaction, ts: int
  1025. ) -> None:
  1026. sql = """
  1027. DELETE FROM threepid_validation_token WHERE
  1028. expires < ?
  1029. """
  1030. txn.execute(sql, (ts,))
  1031. await self.db_pool.runInteraction(
  1032. "cull_expired_threepid_validation_tokens",
  1033. cull_expired_threepid_validation_tokens_txn,
  1034. self._clock.time_msec(),
  1035. )
  1036. @wrap_as_background_process("account_validity_set_expiration_dates")
  1037. async def _set_expiration_date_when_missing(self) -> None:
  1038. """
  1039. Retrieves the list of registered users that don't have an expiration date, and
  1040. adds an expiration date for each of them.
  1041. """
  1042. def select_users_with_no_expiration_date_txn(txn: LoggingTransaction) -> None:
  1043. """Retrieves the list of registered users with no expiration date from the
  1044. database, filtering out deactivated users.
  1045. """
  1046. sql = (
  1047. "SELECT users.name FROM users"
  1048. " LEFT JOIN account_validity ON (users.name = account_validity.user_id)"
  1049. " WHERE account_validity.user_id is NULL AND users.deactivated = 0;"
  1050. )
  1051. txn.execute(sql, [])
  1052. res = self.db_pool.cursor_to_dict(txn)
  1053. if res:
  1054. for user in res:
  1055. self.set_expiration_date_for_user_txn(
  1056. txn, user["name"], use_delta=True
  1057. )
  1058. await self.db_pool.runInteraction(
  1059. "get_users_with_no_expiration_date",
  1060. select_users_with_no_expiration_date_txn,
  1061. )
  1062. def set_expiration_date_for_user_txn(
  1063. self, txn: LoggingTransaction, user_id: str, use_delta: bool = False
  1064. ) -> None:
  1065. """Sets an expiration date to the account with the given user ID.
  1066. Args:
  1067. user_id: User ID to set an expiration date for.
  1068. use_delta: If set to False, the expiration date for the user will be
  1069. now + validity period. If set to True, this expiration date will be a
  1070. random value in the [now + period - d ; now + period] range, d being a
  1071. delta equal to 10% of the validity period.
  1072. """
  1073. now_ms = self._clock.time_msec()
  1074. assert self._account_validity_period is not None
  1075. expiration_ts = now_ms + self._account_validity_period
  1076. if use_delta:
  1077. assert self._account_validity_startup_job_max_delta is not None
  1078. expiration_ts = random.randrange(
  1079. int(expiration_ts - self._account_validity_startup_job_max_delta),
  1080. expiration_ts,
  1081. )
  1082. self.db_pool.simple_upsert_txn(
  1083. txn,
  1084. "account_validity",
  1085. keyvalues={"user_id": user_id},
  1086. values={"expiration_ts_ms": expiration_ts, "email_sent": False},
  1087. )
  1088. async def get_user_pending_deactivation(self) -> Optional[str]:
  1089. """
  1090. Gets one user from the table of users waiting to be parted from all the rooms
  1091. they're in.
  1092. """
  1093. return await self.db_pool.simple_select_one_onecol(
  1094. "users_pending_deactivation",
  1095. keyvalues={},
  1096. retcol="user_id",
  1097. allow_none=True,
  1098. desc="get_users_pending_deactivation",
  1099. )
  1100. async def del_user_pending_deactivation(self, user_id: str) -> None:
  1101. """
  1102. Removes the given user to the table of users who need to be parted from all the
  1103. rooms they're in, effectively marking that user as fully deactivated.
  1104. """
  1105. # XXX: This should be simple_delete_one but we failed to put a unique index on
  1106. # the table, so somehow duplicate entries have ended up in it.
  1107. await self.db_pool.simple_delete(
  1108. "users_pending_deactivation",
  1109. keyvalues={"user_id": user_id},
  1110. desc="del_user_pending_deactivation",
  1111. )
  1112. async def get_access_token_last_validated(self, token_id: int) -> int:
  1113. """Retrieves the time (in milliseconds) of the last validation of an access token.
  1114. Args:
  1115. token_id: The ID of the access token to update.
  1116. Raises:
  1117. StoreError if the access token was not found.
  1118. Returns:
  1119. The last validation time.
  1120. """
  1121. result = await self.db_pool.simple_select_one_onecol(
  1122. "access_tokens", {"id": token_id}, "last_validated"
  1123. )
  1124. # If this token has not been validated (since starting to track this),
  1125. # return 0 instead of None.
  1126. return result or 0
  1127. async def update_access_token_last_validated(self, token_id: int) -> None:
  1128. """Updates the last time an access token was validated.
  1129. Args:
  1130. token_id: The ID of the access token to update.
  1131. Raises:
  1132. StoreError if there was a problem updating this.
  1133. """
  1134. now = self._clock.time_msec()
  1135. await self.db_pool.simple_update_one(
  1136. "access_tokens",
  1137. {"id": token_id},
  1138. {"last_validated": now},
  1139. desc="update_access_token_last_validated",
  1140. )
  1141. async def registration_token_is_valid(self, token: str) -> bool:
  1142. """Checks if a token can be used to authenticate a registration.
  1143. Args:
  1144. token: The registration token to be checked
  1145. Returns:
  1146. True if the token is valid, False otherwise.
  1147. """
  1148. res = await self.db_pool.simple_select_one(
  1149. "registration_tokens",
  1150. keyvalues={"token": token},
  1151. retcols=["uses_allowed", "pending", "completed", "expiry_time"],
  1152. allow_none=True,
  1153. )
  1154. # Check if the token exists
  1155. if res is None:
  1156. return False
  1157. # Check if the token has expired
  1158. now = self._clock.time_msec()
  1159. if res["expiry_time"] and res["expiry_time"] < now:
  1160. return False
  1161. # Check if the token has been used up
  1162. if (
  1163. res["uses_allowed"]
  1164. and res["pending"] + res["completed"] >= res["uses_allowed"]
  1165. ):
  1166. return False
  1167. # Otherwise, the token is valid
  1168. return True
  1169. async def set_registration_token_pending(self, token: str) -> None:
  1170. """Increment the pending registrations counter for a token.
  1171. Args:
  1172. token: The registration token pending use
  1173. """
  1174. def _set_registration_token_pending_txn(txn: LoggingTransaction) -> None:
  1175. pending = self.db_pool.simple_select_one_onecol_txn(
  1176. txn,
  1177. "registration_tokens",
  1178. keyvalues={"token": token},
  1179. retcol="pending",
  1180. )
  1181. self.db_pool.simple_update_one_txn(
  1182. txn,
  1183. "registration_tokens",
  1184. keyvalues={"token": token},
  1185. updatevalues={"pending": pending + 1},
  1186. )
  1187. await self.db_pool.runInteraction(
  1188. "set_registration_token_pending", _set_registration_token_pending_txn
  1189. )
  1190. async def use_registration_token(self, token: str) -> None:
  1191. """Complete a use of the given registration token.
  1192. The `pending` counter will be decremented, and the `completed`
  1193. counter will be incremented.
  1194. Args:
  1195. token: The registration token to be 'used'
  1196. """
  1197. def _use_registration_token_txn(txn: LoggingTransaction) -> None:
  1198. # Normally, res is Optional[Dict[str, Any]].
  1199. # Override type because the return type is only optional if
  1200. # allow_none is True, and we don't want mypy throwing errors
  1201. # about None not being indexable.
  1202. res = cast(
  1203. Dict[str, Any],
  1204. self.db_pool.simple_select_one_txn(
  1205. txn,
  1206. "registration_tokens",
  1207. keyvalues={"token": token},
  1208. retcols=["pending", "completed"],
  1209. ),
  1210. )
  1211. # Decrement pending and increment completed
  1212. self.db_pool.simple_update_one_txn(
  1213. txn,
  1214. "registration_tokens",
  1215. keyvalues={"token": token},
  1216. updatevalues={
  1217. "completed": res["completed"] + 1,
  1218. "pending": res["pending"] - 1,
  1219. },
  1220. )
  1221. await self.db_pool.runInteraction(
  1222. "use_registration_token", _use_registration_token_txn
  1223. )
  1224. async def get_registration_tokens(
  1225. self, valid: Optional[bool] = None
  1226. ) -> List[Dict[str, Any]]:
  1227. """List all registration tokens. Used by the admin API.
  1228. Args:
  1229. valid: If True, only valid tokens are returned.
  1230. If False, only invalid tokens are returned.
  1231. Default is None: return all tokens regardless of validity.
  1232. Returns:
  1233. A list of dicts, each containing details of a token.
  1234. """
  1235. def select_registration_tokens_txn(
  1236. txn: LoggingTransaction, now: int, valid: Optional[bool]
  1237. ) -> List[Dict[str, Any]]:
  1238. if valid is None:
  1239. # Return all tokens regardless of validity
  1240. txn.execute("SELECT * FROM registration_tokens")
  1241. elif valid:
  1242. # Select valid tokens only
  1243. sql = (
  1244. "SELECT * FROM registration_tokens WHERE "
  1245. "(uses_allowed > pending + completed OR uses_allowed IS NULL) "
  1246. "AND (expiry_time > ? OR expiry_time IS NULL)"
  1247. )
  1248. txn.execute(sql, [now])
  1249. else:
  1250. # Select invalid tokens only
  1251. sql = (
  1252. "SELECT * FROM registration_tokens WHERE "
  1253. "uses_allowed <= pending + completed OR expiry_time <= ?"
  1254. )
  1255. txn.execute(sql, [now])
  1256. return self.db_pool.cursor_to_dict(txn)
  1257. return await self.db_pool.runInteraction(
  1258. "select_registration_tokens",
  1259. select_registration_tokens_txn,
  1260. self._clock.time_msec(),
  1261. valid,
  1262. )
  1263. async def get_one_registration_token(self, token: str) -> Optional[Dict[str, Any]]:
  1264. """Get info about the given registration token. Used by the admin API.
  1265. Args:
  1266. token: The token to retrieve information about.
  1267. Returns:
  1268. A dict, or None if token doesn't exist.
  1269. """
  1270. return await self.db_pool.simple_select_one(
  1271. "registration_tokens",
  1272. keyvalues={"token": token},
  1273. retcols=["token", "uses_allowed", "pending", "completed", "expiry_time"],
  1274. allow_none=True,
  1275. desc="get_one_registration_token",
  1276. )
  1277. async def generate_registration_token(
  1278. self, length: int, chars: str
  1279. ) -> Optional[str]:
  1280. """Generate a random registration token. Used by the admin API.
  1281. Args:
  1282. length: The length of the token to generate.
  1283. chars: A string of the characters allowed in the generated token.
  1284. Returns:
  1285. The generated token.
  1286. Raises:
  1287. SynapseError if a unique registration token could still not be
  1288. generated after a few tries.
  1289. """
  1290. # Make a few attempts at generating a unique token of the required
  1291. # length before failing.
  1292. for _i in range(3):
  1293. # Generate token
  1294. token = "".join(random.choices(chars, k=length))
  1295. # Check if the token already exists
  1296. existing_token = await self.db_pool.simple_select_one_onecol(
  1297. "registration_tokens",
  1298. keyvalues={"token": token},
  1299. retcol="token",
  1300. allow_none=True,
  1301. desc="check_if_registration_token_exists",
  1302. )
  1303. if existing_token is None:
  1304. # The generated token doesn't exist yet, return it
  1305. return token
  1306. raise SynapseError(
  1307. 500,
  1308. "Unable to generate a unique registration token. Try again with a greater length",
  1309. Codes.UNKNOWN,
  1310. )
  1311. async def create_registration_token(
  1312. self, token: str, uses_allowed: Optional[int], expiry_time: Optional[int]
  1313. ) -> bool:
  1314. """Create a new registration token. Used by the admin API.
  1315. Args:
  1316. token: The token to create.
  1317. uses_allowed: The number of times the token can be used to complete
  1318. a registration before it becomes invalid. A value of None indicates
  1319. unlimited uses.
  1320. expiry_time: The latest time the token is valid. Given as the
  1321. number of milliseconds since 1970-01-01 00:00:00 UTC. A value of
  1322. None indicates that the token does not expire.
  1323. Returns:
  1324. Whether the row was inserted or not.
  1325. """
  1326. def _create_registration_token_txn(txn: LoggingTransaction) -> bool:
  1327. row = self.db_pool.simple_select_one_txn(
  1328. txn,
  1329. "registration_tokens",
  1330. keyvalues={"token": token},
  1331. retcols=["token"],
  1332. allow_none=True,
  1333. )
  1334. if row is not None:
  1335. # Token already exists
  1336. return False
  1337. self.db_pool.simple_insert_txn(
  1338. txn,
  1339. "registration_tokens",
  1340. values={
  1341. "token": token,
  1342. "uses_allowed": uses_allowed,
  1343. "pending": 0,
  1344. "completed": 0,
  1345. "expiry_time": expiry_time,
  1346. },
  1347. )
  1348. return True
  1349. return await self.db_pool.runInteraction(
  1350. "create_registration_token", _create_registration_token_txn
  1351. )
  1352. async def update_registration_token(
  1353. self, token: str, updatevalues: Dict[str, Optional[int]]
  1354. ) -> Optional[Dict[str, Any]]:
  1355. """Update a registration token. Used by the admin API.
  1356. Args:
  1357. token: The token to update.
  1358. updatevalues: A dict with the fields to update. E.g.:
  1359. `{"uses_allowed": 3}` to update just uses_allowed, or
  1360. `{"uses_allowed": 3, "expiry_time": None}` to update both.
  1361. This is passed straight to simple_update_one.
  1362. Returns:
  1363. A dict with all info about the token, or None if token doesn't exist.
  1364. """
  1365. def _update_registration_token_txn(
  1366. txn: LoggingTransaction,
  1367. ) -> Optional[Dict[str, Any]]:
  1368. try:
  1369. self.db_pool.simple_update_one_txn(
  1370. txn,
  1371. "registration_tokens",
  1372. keyvalues={"token": token},
  1373. updatevalues=updatevalues,
  1374. )
  1375. except StoreError:
  1376. # Update failed because token does not exist
  1377. return None
  1378. # Get all info about the token so it can be sent in the response
  1379. return self.db_pool.simple_select_one_txn(
  1380. txn,
  1381. "registration_tokens",
  1382. keyvalues={"token": token},
  1383. retcols=[
  1384. "token",
  1385. "uses_allowed",
  1386. "pending",
  1387. "completed",
  1388. "expiry_time",
  1389. ],
  1390. allow_none=True,
  1391. )
  1392. return await self.db_pool.runInteraction(
  1393. "update_registration_token", _update_registration_token_txn
  1394. )
  1395. async def delete_registration_token(self, token: str) -> bool:
  1396. """Delete a registration token. Used by the admin API.
  1397. Args:
  1398. token: The token to delete.
  1399. Returns:
  1400. Whether the token was successfully deleted or not.
  1401. """
  1402. try:
  1403. await self.db_pool.simple_delete_one(
  1404. "registration_tokens",
  1405. keyvalues={"token": token},
  1406. desc="delete_registration_token",
  1407. )
  1408. except StoreError:
  1409. # Deletion failed because token does not exist
  1410. return False
  1411. return True
  1412. @cached()
  1413. async def mark_access_token_as_used(self, token_id: int) -> None:
  1414. """
  1415. Mark the access token as used, which invalidates the refresh token used
  1416. to obtain it.
  1417. Because get_user_by_access_token is cached, this function might be
  1418. called multiple times for the same token, effectively doing unnecessary
  1419. SQL updates. Because updating the `used` field only goes one way (from
  1420. False to True) it is safe to cache this function as well to avoid this
  1421. issue.
  1422. Args:
  1423. token_id: The ID of the access token to update.
  1424. Raises:
  1425. StoreError if there was a problem updating this.
  1426. """
  1427. await self.db_pool.simple_update_one(
  1428. "access_tokens",
  1429. {"id": token_id},
  1430. {"used": True},
  1431. desc="mark_access_token_as_used",
  1432. )
  1433. async def lookup_refresh_token(
  1434. self, token: str
  1435. ) -> Optional[RefreshTokenLookupResult]:
  1436. """Lookup a refresh token with hints about its validity."""
  1437. def _lookup_refresh_token_txn(
  1438. txn: LoggingTransaction,
  1439. ) -> Optional[RefreshTokenLookupResult]:
  1440. txn.execute(
  1441. """
  1442. SELECT
  1443. rt.id token_id,
  1444. rt.user_id,
  1445. rt.device_id,
  1446. rt.next_token_id,
  1447. (nrt.next_token_id IS NOT NULL) AS has_next_refresh_token_been_refreshed,
  1448. at.used AS has_next_access_token_been_used,
  1449. rt.expiry_ts,
  1450. rt.ultimate_session_expiry_ts
  1451. FROM refresh_tokens rt
  1452. LEFT JOIN refresh_tokens nrt ON rt.next_token_id = nrt.id
  1453. LEFT JOIN access_tokens at ON at.refresh_token_id = nrt.id
  1454. WHERE rt.token = ?
  1455. """,
  1456. (token,),
  1457. )
  1458. row = txn.fetchone()
  1459. if row is None:
  1460. return None
  1461. return RefreshTokenLookupResult(
  1462. token_id=row[0],
  1463. user_id=row[1],
  1464. device_id=row[2],
  1465. next_token_id=row[3],
  1466. # SQLite returns 0 or 1 for false/true, so convert to a bool.
  1467. has_next_refresh_token_been_refreshed=bool(row[4]),
  1468. # This column is nullable, ensure it's a boolean
  1469. has_next_access_token_been_used=(row[5] or False),
  1470. expiry_ts=row[6],
  1471. ultimate_session_expiry_ts=row[7],
  1472. )
  1473. return await self.db_pool.runInteraction(
  1474. "lookup_refresh_token", _lookup_refresh_token_txn
  1475. )
  1476. async def replace_refresh_token(self, token_id: int, next_token_id: int) -> None:
  1477. """
  1478. Set the successor of a refresh token, removing the existing successor
  1479. if any.
  1480. This also deletes the predecessor refresh and access tokens,
  1481. since they cannot be valid anymore.
  1482. Args:
  1483. token_id: ID of the refresh token to update.
  1484. next_token_id: ID of its successor.
  1485. """
  1486. def _replace_refresh_token_txn(txn: LoggingTransaction) -> None:
  1487. # First check if there was an existing refresh token
  1488. old_next_token_id = self.db_pool.simple_select_one_onecol_txn(
  1489. txn,
  1490. "refresh_tokens",
  1491. {"id": token_id},
  1492. "next_token_id",
  1493. allow_none=True,
  1494. )
  1495. self.db_pool.simple_update_one_txn(
  1496. txn,
  1497. "refresh_tokens",
  1498. {"id": token_id},
  1499. {"next_token_id": next_token_id},
  1500. )
  1501. # Delete the old "next" token if it exists. This should cascade and
  1502. # delete the associated access_token
  1503. if old_next_token_id is not None:
  1504. self.db_pool.simple_delete_one_txn(
  1505. txn,
  1506. "refresh_tokens",
  1507. {"id": old_next_token_id},
  1508. )
  1509. # Delete the previous refresh token, since we only want to keep the
  1510. # last 2 refresh tokens in the database.
  1511. # (The predecessor of the latest refresh token is still useful in
  1512. # case the refresh was interrupted and the client re-uses the old
  1513. # one.)
  1514. # This cascades to delete the associated access token.
  1515. self.db_pool.simple_delete_txn(
  1516. txn, "refresh_tokens", {"next_token_id": token_id}
  1517. )
  1518. await self.db_pool.runInteraction(
  1519. "replace_refresh_token", _replace_refresh_token_txn
  1520. )
  1521. async def add_login_token_to_user(
  1522. self,
  1523. user_id: str,
  1524. token: str,
  1525. expiry_ts: int,
  1526. auth_provider_id: Optional[str],
  1527. auth_provider_session_id: Optional[str],
  1528. ) -> None:
  1529. """Adds a short-term login token for the given user.
  1530. Args:
  1531. user_id: The user ID.
  1532. token: The new login token to add.
  1533. expiry_ts (milliseconds since the epoch): Time after which the login token
  1534. cannot be used.
  1535. auth_provider_id: The SSO Identity Provider that the user authenticated with
  1536. to get this token, if any
  1537. auth_provider_session_id: The session ID advertised by the SSO Identity
  1538. Provider, if any.
  1539. """
  1540. await self.db_pool.simple_insert(
  1541. "login_tokens",
  1542. {
  1543. "token": token,
  1544. "user_id": user_id,
  1545. "expiry_ts": expiry_ts,
  1546. "auth_provider_id": auth_provider_id,
  1547. "auth_provider_session_id": auth_provider_session_id,
  1548. },
  1549. desc="add_login_token_to_user",
  1550. )
  1551. def _consume_login_token(
  1552. self,
  1553. txn: LoggingTransaction,
  1554. token: str,
  1555. ts: int,
  1556. ) -> LoginTokenLookupResult:
  1557. values = self.db_pool.simple_select_one_txn(
  1558. txn,
  1559. "login_tokens",
  1560. keyvalues={"token": token},
  1561. retcols=(
  1562. "user_id",
  1563. "expiry_ts",
  1564. "used_ts",
  1565. "auth_provider_id",
  1566. "auth_provider_session_id",
  1567. ),
  1568. allow_none=True,
  1569. )
  1570. if values is None:
  1571. raise NotFoundError()
  1572. self.db_pool.simple_update_one_txn(
  1573. txn,
  1574. "login_tokens",
  1575. keyvalues={"token": token},
  1576. updatevalues={"used_ts": ts},
  1577. )
  1578. user_id = values["user_id"]
  1579. expiry_ts = values["expiry_ts"]
  1580. used_ts = values["used_ts"]
  1581. auth_provider_id = values["auth_provider_id"]
  1582. auth_provider_session_id = values["auth_provider_session_id"]
  1583. # Token was already used
  1584. if used_ts is not None:
  1585. raise LoginTokenReused()
  1586. # Token expired
  1587. if ts > int(expiry_ts):
  1588. raise LoginTokenExpired()
  1589. return LoginTokenLookupResult(
  1590. user_id=user_id,
  1591. auth_provider_id=auth_provider_id,
  1592. auth_provider_session_id=auth_provider_session_id,
  1593. )
  1594. async def consume_login_token(self, token: str) -> LoginTokenLookupResult:
  1595. """Lookup a login token and consume it.
  1596. Args:
  1597. token: The login token.
  1598. Returns:
  1599. The data stored with that token, including the `user_id`. Returns `None` if
  1600. the token does not exist or if it expired.
  1601. Raises:
  1602. NotFound if the login token was not found in database
  1603. LoginTokenExpired if the login token expired
  1604. LoginTokenReused if the login token was already used
  1605. """
  1606. return await self.db_pool.runInteraction(
  1607. "consume_login_token",
  1608. self._consume_login_token,
  1609. token,
  1610. self._clock.time_msec(),
  1611. )
  1612. async def invalidate_login_tokens_by_session_id(
  1613. self, auth_provider_id: str, auth_provider_session_id: str
  1614. ) -> None:
  1615. """Invalidate login tokens with the given IdP session ID.
  1616. Args:
  1617. auth_provider_id: The SSO Identity Provider that the user authenticated with
  1618. to get this token
  1619. auth_provider_session_id: The session ID advertised by the SSO Identity
  1620. Provider
  1621. """
  1622. await self.db_pool.simple_update(
  1623. table="login_tokens",
  1624. keyvalues={
  1625. "auth_provider_id": auth_provider_id,
  1626. "auth_provider_session_id": auth_provider_session_id,
  1627. },
  1628. updatevalues={"used_ts": self._clock.time_msec()},
  1629. desc="invalidate_login_tokens_by_session_id",
  1630. )
  1631. @cached()
  1632. async def is_guest(self, user_id: str) -> bool:
  1633. res = await self.db_pool.simple_select_one_onecol(
  1634. table="users",
  1635. keyvalues={"name": user_id},
  1636. retcol="is_guest",
  1637. allow_none=True,
  1638. desc="is_guest",
  1639. )
  1640. return res if res else False
  1641. @cached()
  1642. async def is_user_approved(self, user_id: str) -> bool:
  1643. """Checks if a user is approved and therefore can be allowed to log in.
  1644. If the user's 'approved' column is NULL, we consider it as true given it means
  1645. the user was registered when support for an approval flow was either disabled
  1646. or nonexistent.
  1647. Args:
  1648. user_id: the user to check the approval status of.
  1649. Returns:
  1650. A boolean that is True if the user is approved, False otherwise.
  1651. """
  1652. def is_user_approved_txn(txn: LoggingTransaction) -> bool:
  1653. txn.execute(
  1654. """
  1655. SELECT COALESCE(approved, TRUE) AS approved FROM users WHERE name = ?
  1656. """,
  1657. (user_id,),
  1658. )
  1659. rows = self.db_pool.cursor_to_dict(txn)
  1660. # We cast to bool because the value returned by the database engine might
  1661. # be an integer if we're using SQLite.
  1662. return bool(rows[0]["approved"])
  1663. return await self.db_pool.runInteraction(
  1664. desc="is_user_pending_approval",
  1665. func=is_user_approved_txn,
  1666. )
  1667. class RegistrationBackgroundUpdateStore(RegistrationWorkerStore):
  1668. def __init__(
  1669. self,
  1670. database: DatabasePool,
  1671. db_conn: LoggingDatabaseConnection,
  1672. hs: "HomeServer",
  1673. ):
  1674. super().__init__(database, db_conn, hs)
  1675. self._clock = hs.get_clock()
  1676. self.config = hs.config
  1677. self.db_pool.updates.register_background_index_update(
  1678. "access_tokens_device_index",
  1679. index_name="access_tokens_device_id",
  1680. table="access_tokens",
  1681. columns=["user_id", "device_id"],
  1682. )
  1683. self.db_pool.updates.register_background_index_update(
  1684. "users_creation_ts",
  1685. index_name="users_creation_ts",
  1686. table="users",
  1687. columns=["creation_ts"],
  1688. )
  1689. self.db_pool.updates.register_background_update_handler(
  1690. "users_set_deactivated_flag", self._background_update_set_deactivated_flag
  1691. )
  1692. self.db_pool.updates.register_background_index_update(
  1693. "user_external_ids_user_id_idx",
  1694. index_name="user_external_ids_user_id_idx",
  1695. table="user_external_ids",
  1696. columns=["user_id"],
  1697. unique=False,
  1698. )
  1699. async def _background_update_set_deactivated_flag(
  1700. self, progress: JsonDict, batch_size: int
  1701. ) -> int:
  1702. """Retrieves a list of all deactivated users and sets the 'deactivated' flag to 1
  1703. for each of them.
  1704. """
  1705. last_user = progress.get("user_id", "")
  1706. def _background_update_set_deactivated_flag_txn(
  1707. txn: LoggingTransaction,
  1708. ) -> Tuple[bool, int]:
  1709. txn.execute(
  1710. """
  1711. SELECT
  1712. users.name,
  1713. COUNT(access_tokens.token) AS count_tokens,
  1714. COUNT(user_threepids.address) AS count_threepids
  1715. FROM users
  1716. LEFT JOIN access_tokens ON (access_tokens.user_id = users.name)
  1717. LEFT JOIN user_threepids ON (user_threepids.user_id = users.name)
  1718. WHERE (users.password_hash IS NULL OR users.password_hash = '')
  1719. AND (users.appservice_id IS NULL OR users.appservice_id = '')
  1720. AND users.is_guest = 0
  1721. AND users.name > ?
  1722. GROUP BY users.name
  1723. ORDER BY users.name ASC
  1724. LIMIT ?;
  1725. """,
  1726. (last_user, batch_size),
  1727. )
  1728. rows = self.db_pool.cursor_to_dict(txn)
  1729. if not rows:
  1730. return True, 0
  1731. rows_processed_nb = 0
  1732. for user in rows:
  1733. if not user["count_tokens"] and not user["count_threepids"]:
  1734. self.set_user_deactivated_status_txn(txn, user["name"], True)
  1735. rows_processed_nb += 1
  1736. logger.info("Marked %d rows as deactivated", rows_processed_nb)
  1737. self.db_pool.updates._background_update_progress_txn(
  1738. txn, "users_set_deactivated_flag", {"user_id": rows[-1]["name"]}
  1739. )
  1740. if batch_size > len(rows):
  1741. return True, len(rows)
  1742. else:
  1743. return False, len(rows)
  1744. end, nb_processed = await self.db_pool.runInteraction(
  1745. "users_set_deactivated_flag", _background_update_set_deactivated_flag_txn
  1746. )
  1747. if end:
  1748. await self.db_pool.updates._end_background_update(
  1749. "users_set_deactivated_flag"
  1750. )
  1751. return nb_processed
  1752. async def set_user_deactivated_status(
  1753. self, user_id: str, deactivated: bool
  1754. ) -> None:
  1755. """Set the `deactivated` property for the provided user to the provided value.
  1756. Args:
  1757. user_id: The ID of the user to set the status for.
  1758. deactivated: The value to set for `deactivated`.
  1759. """
  1760. await self.db_pool.runInteraction(
  1761. "set_user_deactivated_status",
  1762. self.set_user_deactivated_status_txn,
  1763. user_id,
  1764. deactivated,
  1765. )
  1766. def set_user_deactivated_status_txn(
  1767. self, txn: LoggingTransaction, user_id: str, deactivated: bool
  1768. ) -> None:
  1769. self.db_pool.simple_update_one_txn(
  1770. txn=txn,
  1771. table="users",
  1772. keyvalues={"name": user_id},
  1773. updatevalues={"deactivated": 1 if deactivated else 0},
  1774. )
  1775. self._invalidate_cache_and_stream(
  1776. txn, self.get_user_deactivated_status, (user_id,)
  1777. )
  1778. self._invalidate_cache_and_stream(txn, self.get_user_by_id, (user_id,))
  1779. txn.call_after(self.is_guest.invalidate, (user_id,))
  1780. def update_user_approval_status_txn(
  1781. self, txn: LoggingTransaction, user_id: str, approved: bool
  1782. ) -> None:
  1783. """Set the user's 'approved' flag to the given value.
  1784. The boolean is turned into an int because the column is a smallint.
  1785. Args:
  1786. txn: the current database transaction.
  1787. user_id: the user to update the flag for.
  1788. approved: the value to set the flag to.
  1789. """
  1790. self.db_pool.simple_update_one_txn(
  1791. txn=txn,
  1792. table="users",
  1793. keyvalues={"name": user_id},
  1794. updatevalues={"approved": approved},
  1795. )
  1796. # Invalidate the caches of methods that read the value of the 'approved' flag.
  1797. self._invalidate_cache_and_stream(txn, self.get_user_by_id, (user_id,))
  1798. self._invalidate_cache_and_stream(txn, self.is_user_approved, (user_id,))
  1799. class RegistrationStore(StatsStore, RegistrationBackgroundUpdateStore):
  1800. def __init__(
  1801. self,
  1802. database: DatabasePool,
  1803. db_conn: LoggingDatabaseConnection,
  1804. hs: "HomeServer",
  1805. ):
  1806. super().__init__(database, db_conn, hs)
  1807. self._ignore_unknown_session_error = (
  1808. hs.config.server.request_token_inhibit_3pid_errors
  1809. )
  1810. self._access_tokens_id_gen = IdGenerator(db_conn, "access_tokens", "id")
  1811. self._refresh_tokens_id_gen = IdGenerator(db_conn, "refresh_tokens", "id")
  1812. # If support for MSC3866 is enabled and configured to require approval for new
  1813. # account, we will create new users with an 'approved' flag set to false.
  1814. self._require_approval = (
  1815. hs.config.experimental.msc3866.enabled
  1816. and hs.config.experimental.msc3866.require_approval_for_new_accounts
  1817. )
  1818. # Create a background job for removing expired login tokens
  1819. if hs.config.worker.run_background_tasks:
  1820. self._clock.looping_call(
  1821. self._delete_expired_login_tokens, THIRTY_MINUTES_IN_MS
  1822. )
  1823. async def add_access_token_to_user(
  1824. self,
  1825. user_id: str,
  1826. token: str,
  1827. device_id: Optional[str],
  1828. valid_until_ms: Optional[int],
  1829. puppets_user_id: Optional[str] = None,
  1830. refresh_token_id: Optional[int] = None,
  1831. ) -> int:
  1832. """Adds an access token for the given user.
  1833. Args:
  1834. user_id: The user ID.
  1835. token: The new access token to add.
  1836. device_id: ID of the device to associate with the access token.
  1837. valid_until_ms: when the token is valid until. None for no expiry.
  1838. puppets_user_id
  1839. refresh_token_id: ID of the refresh token generated alongside this
  1840. access token.
  1841. Raises:
  1842. StoreError if there was a problem adding this.
  1843. Returns:
  1844. The token ID
  1845. """
  1846. next_id = self._access_tokens_id_gen.get_next()
  1847. now = self._clock.time_msec()
  1848. await self.db_pool.simple_insert(
  1849. "access_tokens",
  1850. {
  1851. "id": next_id,
  1852. "user_id": user_id,
  1853. "token": token,
  1854. "device_id": device_id,
  1855. "valid_until_ms": valid_until_ms,
  1856. "puppets_user_id": puppets_user_id,
  1857. "last_validated": now,
  1858. "refresh_token_id": refresh_token_id,
  1859. "used": False,
  1860. },
  1861. desc="add_access_token_to_user",
  1862. )
  1863. return next_id
  1864. async def add_refresh_token_to_user(
  1865. self,
  1866. user_id: str,
  1867. token: str,
  1868. device_id: Optional[str],
  1869. expiry_ts: Optional[int],
  1870. ultimate_session_expiry_ts: Optional[int],
  1871. ) -> int:
  1872. """Adds a refresh token for the given user.
  1873. Args:
  1874. user_id: The user ID.
  1875. token: The new access token to add.
  1876. device_id: ID of the device to associate with the refresh token.
  1877. expiry_ts (milliseconds since the epoch): Time after which the
  1878. refresh token cannot be used.
  1879. If None, the refresh token never expires until it has been used.
  1880. ultimate_session_expiry_ts (milliseconds since the epoch):
  1881. Time at which the session will end and can not be extended any
  1882. further.
  1883. If None, the session can be refreshed indefinitely.
  1884. Raises:
  1885. StoreError if there was a problem adding this.
  1886. Returns:
  1887. The token ID
  1888. """
  1889. next_id = self._refresh_tokens_id_gen.get_next()
  1890. await self.db_pool.simple_insert(
  1891. "refresh_tokens",
  1892. {
  1893. "id": next_id,
  1894. "user_id": user_id,
  1895. "device_id": device_id,
  1896. "token": token,
  1897. "next_token_id": None,
  1898. "expiry_ts": expiry_ts,
  1899. "ultimate_session_expiry_ts": ultimate_session_expiry_ts,
  1900. },
  1901. desc="add_refresh_token_to_user",
  1902. )
  1903. return next_id
  1904. def _set_device_for_access_token_txn(
  1905. self, txn: LoggingTransaction, token: str, device_id: str
  1906. ) -> str:
  1907. old_device_id = self.db_pool.simple_select_one_onecol_txn(
  1908. txn, "access_tokens", {"token": token}, "device_id"
  1909. )
  1910. self.db_pool.simple_update_txn(
  1911. txn, "access_tokens", {"token": token}, {"device_id": device_id}
  1912. )
  1913. self._invalidate_cache_and_stream(txn, self.get_user_by_access_token, (token,))
  1914. return old_device_id
  1915. async def set_device_for_access_token(self, token: str, device_id: str) -> str:
  1916. """Sets the device ID associated with an access token.
  1917. Args:
  1918. token: The access token to modify.
  1919. device_id: The new device ID.
  1920. Returns:
  1921. The old device ID associated with the access token.
  1922. """
  1923. return await self.db_pool.runInteraction(
  1924. "set_device_for_access_token",
  1925. self._set_device_for_access_token_txn,
  1926. token,
  1927. device_id,
  1928. )
  1929. async def register_user(
  1930. self,
  1931. user_id: str,
  1932. password_hash: Optional[str] = None,
  1933. was_guest: bool = False,
  1934. make_guest: bool = False,
  1935. appservice_id: Optional[str] = None,
  1936. create_profile_with_displayname: Optional[str] = None,
  1937. admin: bool = False,
  1938. user_type: Optional[str] = None,
  1939. shadow_banned: bool = False,
  1940. approved: bool = False,
  1941. ) -> None:
  1942. """Attempts to register an account.
  1943. Args:
  1944. user_id: The desired user ID to register.
  1945. password_hash: Optional. The password hash for this user.
  1946. was_guest: Whether this is a guest account being upgraded to a
  1947. non-guest account.
  1948. make_guest: True if the the new user should be guest, false to add a
  1949. regular user account.
  1950. appservice_id: The ID of the appservice registering the user.
  1951. create_profile_with_displayname: Optionally create a profile for
  1952. the user, setting their displayname to the given value
  1953. admin: is an admin user?
  1954. user_type: type of user. One of the values from api.constants.UserTypes,
  1955. or None for a normal user.
  1956. shadow_banned: Whether the user is shadow-banned, i.e. they may be
  1957. told their requests succeeded but we ignore them.
  1958. approved: Whether to consider the user has already been approved by an
  1959. administrator.
  1960. Raises:
  1961. StoreError if the user_id could not be registered.
  1962. """
  1963. await self.db_pool.runInteraction(
  1964. "register_user",
  1965. self._register_user,
  1966. user_id,
  1967. password_hash,
  1968. was_guest,
  1969. make_guest,
  1970. appservice_id,
  1971. create_profile_with_displayname,
  1972. admin,
  1973. user_type,
  1974. shadow_banned,
  1975. approved,
  1976. )
  1977. def _register_user(
  1978. self,
  1979. txn: LoggingTransaction,
  1980. user_id: str,
  1981. password_hash: Optional[str],
  1982. was_guest: bool,
  1983. make_guest: bool,
  1984. appservice_id: Optional[str],
  1985. create_profile_with_displayname: Optional[str],
  1986. admin: bool,
  1987. user_type: Optional[str],
  1988. shadow_banned: bool,
  1989. approved: bool,
  1990. ) -> None:
  1991. user_id_obj = UserID.from_string(user_id)
  1992. now = int(self._clock.time())
  1993. user_approved = approved or not self._require_approval
  1994. try:
  1995. if was_guest:
  1996. # Ensure that the guest user actually exists
  1997. # ``allow_none=False`` makes this raise an exception
  1998. # if the row isn't in the database.
  1999. self.db_pool.simple_select_one_txn(
  2000. txn,
  2001. "users",
  2002. keyvalues={"name": user_id, "is_guest": 1},
  2003. retcols=("name",),
  2004. allow_none=False,
  2005. )
  2006. self.db_pool.simple_update_one_txn(
  2007. txn,
  2008. "users",
  2009. keyvalues={"name": user_id, "is_guest": 1},
  2010. updatevalues={
  2011. "password_hash": password_hash,
  2012. "upgrade_ts": now,
  2013. "is_guest": 1 if make_guest else 0,
  2014. "appservice_id": appservice_id,
  2015. "admin": 1 if admin else 0,
  2016. "user_type": user_type,
  2017. "shadow_banned": shadow_banned,
  2018. "approved": user_approved,
  2019. },
  2020. )
  2021. else:
  2022. self.db_pool.simple_insert_txn(
  2023. txn,
  2024. "users",
  2025. values={
  2026. "name": user_id,
  2027. "password_hash": password_hash,
  2028. "creation_ts": now,
  2029. "is_guest": 1 if make_guest else 0,
  2030. "appservice_id": appservice_id,
  2031. "admin": 1 if admin else 0,
  2032. "user_type": user_type,
  2033. "shadow_banned": shadow_banned,
  2034. "approved": user_approved,
  2035. },
  2036. )
  2037. except self.database_engine.module.IntegrityError:
  2038. raise StoreError(400, "User ID already taken.", errcode=Codes.USER_IN_USE)
  2039. if self._account_validity_enabled:
  2040. self.set_expiration_date_for_user_txn(txn, user_id)
  2041. if create_profile_with_displayname:
  2042. # set a default displayname serverside to avoid ugly race
  2043. # between auto-joins and clients trying to set displaynames
  2044. #
  2045. # *obviously* the 'profiles' table uses localpart for user_id
  2046. # while everything else uses the full mxid.
  2047. txn.execute(
  2048. "INSERT INTO profiles(user_id, displayname) VALUES (?,?)",
  2049. (user_id_obj.localpart, create_profile_with_displayname),
  2050. )
  2051. if self.hs.config.stats.stats_enabled:
  2052. # we create a new completed user statistics row
  2053. # we don't strictly need current_token since this user really can't
  2054. # have any state deltas before now (as it is a new user), but still,
  2055. # we include it for completeness.
  2056. current_token = self._get_max_stream_id_in_current_state_deltas_txn(txn)
  2057. self._update_stats_delta_txn(
  2058. txn, now, "user", user_id, {}, complete_with_stream_id=current_token
  2059. )
  2060. self._invalidate_cache_and_stream(txn, self.get_user_by_id, (user_id,))
  2061. async def user_set_password_hash(
  2062. self, user_id: str, password_hash: Optional[str]
  2063. ) -> None:
  2064. """
  2065. NB. This does *not* evict any cache because the one use for this
  2066. removes most of the entries subsequently anyway so it would be
  2067. pointless. Use flush_user separately.
  2068. """
  2069. def user_set_password_hash_txn(txn: LoggingTransaction) -> None:
  2070. self.db_pool.simple_update_one_txn(
  2071. txn, "users", {"name": user_id}, {"password_hash": password_hash}
  2072. )
  2073. self._invalidate_cache_and_stream(txn, self.get_user_by_id, (user_id,))
  2074. await self.db_pool.runInteraction(
  2075. "user_set_password_hash", user_set_password_hash_txn
  2076. )
  2077. async def user_set_consent_version(
  2078. self, user_id: str, consent_version: str
  2079. ) -> None:
  2080. """Updates the user table to record privacy policy consent
  2081. Args:
  2082. user_id: full mxid of the user to update
  2083. consent_version: version of the policy the user has consented to
  2084. Raises:
  2085. StoreError(404) if user not found
  2086. """
  2087. def f(txn: LoggingTransaction) -> None:
  2088. self.db_pool.simple_update_one_txn(
  2089. txn,
  2090. table="users",
  2091. keyvalues={"name": user_id},
  2092. updatevalues={
  2093. "consent_version": consent_version,
  2094. "consent_ts": self._clock.time_msec(),
  2095. },
  2096. )
  2097. self._invalidate_cache_and_stream(txn, self.get_user_by_id, (user_id,))
  2098. await self.db_pool.runInteraction("user_set_consent_version", f)
  2099. async def user_set_consent_server_notice_sent(
  2100. self, user_id: str, consent_version: str
  2101. ) -> None:
  2102. """Updates the user table to record that we have sent the user a server
  2103. notice about privacy policy consent
  2104. Args:
  2105. user_id: full mxid of the user to update
  2106. consent_version: version of the policy we have notified the user about
  2107. Raises:
  2108. StoreError(404) if user not found
  2109. """
  2110. def f(txn: LoggingTransaction) -> None:
  2111. self.db_pool.simple_update_one_txn(
  2112. txn,
  2113. table="users",
  2114. keyvalues={"name": user_id},
  2115. updatevalues={"consent_server_notice_sent": consent_version},
  2116. )
  2117. self._invalidate_cache_and_stream(txn, self.get_user_by_id, (user_id,))
  2118. await self.db_pool.runInteraction("user_set_consent_server_notice_sent", f)
  2119. async def user_delete_access_tokens(
  2120. self,
  2121. user_id: str,
  2122. except_token_id: Optional[int] = None,
  2123. device_id: Optional[str] = None,
  2124. ) -> List[Tuple[str, int, Optional[str]]]:
  2125. """
  2126. Invalidate access and refresh tokens belonging to a user
  2127. Args:
  2128. user_id: ID of user the tokens belong to
  2129. except_token_id: access_tokens ID which should *not* be deleted
  2130. device_id: ID of device the tokens are associated with.
  2131. If None, tokens associated with any device (or no device) will
  2132. be deleted
  2133. Returns:
  2134. A tuple of (token, token id, device id) for each of the deleted tokens
  2135. """
  2136. def f(txn: LoggingTransaction) -> List[Tuple[str, int, Optional[str]]]:
  2137. keyvalues = {"user_id": user_id}
  2138. if device_id is not None:
  2139. keyvalues["device_id"] = device_id
  2140. items = keyvalues.items()
  2141. where_clause = " AND ".join(k + " = ?" for k, _ in items)
  2142. values: List[Union[str, int]] = [v for _, v in items]
  2143. # Conveniently, refresh_tokens and access_tokens both use the user_id and device_id fields. Only caveat
  2144. # is the `except_token_id` param that is tricky to get right, so for now we're just using the same where
  2145. # clause and values before we handle that. This seems to be only used in the "set password" handler.
  2146. refresh_where_clause = where_clause
  2147. refresh_values = values.copy()
  2148. if except_token_id:
  2149. # TODO: support that for refresh tokens
  2150. where_clause += " AND id != ?"
  2151. values.append(except_token_id)
  2152. txn.execute(
  2153. "SELECT token, id, device_id FROM access_tokens WHERE %s"
  2154. % where_clause,
  2155. values,
  2156. )
  2157. tokens_and_devices = [(r[0], r[1], r[2]) for r in txn]
  2158. for token, _, _ in tokens_and_devices:
  2159. self._invalidate_cache_and_stream(
  2160. txn, self.get_user_by_access_token, (token,)
  2161. )
  2162. txn.execute("DELETE FROM access_tokens WHERE %s" % where_clause, values)
  2163. txn.execute(
  2164. "DELETE FROM refresh_tokens WHERE %s" % refresh_where_clause,
  2165. refresh_values,
  2166. )
  2167. return tokens_and_devices
  2168. return await self.db_pool.runInteraction("user_delete_access_tokens", f)
  2169. async def delete_access_token(self, access_token: str) -> None:
  2170. def f(txn: LoggingTransaction) -> None:
  2171. self.db_pool.simple_delete_one_txn(
  2172. txn, table="access_tokens", keyvalues={"token": access_token}
  2173. )
  2174. self._invalidate_cache_and_stream(
  2175. txn, self.get_user_by_access_token, (access_token,)
  2176. )
  2177. await self.db_pool.runInteraction("delete_access_token", f)
  2178. async def delete_refresh_token(self, refresh_token: str) -> None:
  2179. def f(txn: LoggingTransaction) -> None:
  2180. self.db_pool.simple_delete_one_txn(
  2181. txn, table="refresh_tokens", keyvalues={"token": refresh_token}
  2182. )
  2183. await self.db_pool.runInteraction("delete_refresh_token", f)
  2184. async def add_user_pending_deactivation(self, user_id: str) -> None:
  2185. """
  2186. Adds a user to the table of users who need to be parted from all the rooms they're
  2187. in
  2188. """
  2189. await self.db_pool.simple_insert(
  2190. "users_pending_deactivation",
  2191. values={"user_id": user_id},
  2192. desc="add_user_pending_deactivation",
  2193. )
  2194. async def validate_threepid_session(
  2195. self, session_id: str, client_secret: str, token: str, current_ts: int
  2196. ) -> Optional[str]:
  2197. """Attempt to validate a threepid session using a token
  2198. Args:
  2199. session_id: The id of a validation session
  2200. client_secret: A unique string provided by the client to help identify
  2201. this validation attempt
  2202. token: A validation token
  2203. current_ts: The current unix time in milliseconds. Used for checking
  2204. token expiry status
  2205. Raises:
  2206. ThreepidValidationError: if a matching validation token was not found or has
  2207. expired
  2208. Returns:
  2209. A str representing a link to redirect the user to if there is one.
  2210. """
  2211. # Insert everything into a transaction in order to run atomically
  2212. def validate_threepid_session_txn(txn: LoggingTransaction) -> Optional[str]:
  2213. row = self.db_pool.simple_select_one_txn(
  2214. txn,
  2215. table="threepid_validation_session",
  2216. keyvalues={"session_id": session_id},
  2217. retcols=["client_secret", "validated_at"],
  2218. allow_none=True,
  2219. )
  2220. if not row:
  2221. if self._ignore_unknown_session_error:
  2222. # If we need to inhibit the error caused by an incorrect session ID,
  2223. # use None as placeholder values for the client secret and the
  2224. # validation timestamp.
  2225. # It shouldn't be an issue because they're both only checked after
  2226. # the token check, which should fail. And if it doesn't for some
  2227. # reason, the next check is on the client secret, which is NOT NULL,
  2228. # so we don't have to worry about the client secret matching by
  2229. # accident.
  2230. row = {"client_secret": None, "validated_at": None}
  2231. else:
  2232. raise ThreepidValidationError("Unknown session_id")
  2233. retrieved_client_secret = row["client_secret"]
  2234. validated_at = row["validated_at"]
  2235. row = self.db_pool.simple_select_one_txn(
  2236. txn,
  2237. table="threepid_validation_token",
  2238. keyvalues={"session_id": session_id, "token": token},
  2239. retcols=["expires", "next_link"],
  2240. allow_none=True,
  2241. )
  2242. if not row:
  2243. raise ThreepidValidationError(
  2244. "Validation token not found or has expired"
  2245. )
  2246. expires = row["expires"]
  2247. next_link = row["next_link"]
  2248. if retrieved_client_secret != client_secret:
  2249. raise ThreepidValidationError(
  2250. "This client_secret does not match the provided session_id"
  2251. )
  2252. # If the session is already validated, no need to revalidate
  2253. if validated_at:
  2254. return next_link
  2255. if expires <= current_ts:
  2256. raise ThreepidValidationError(
  2257. "This token has expired. Please request a new one"
  2258. )
  2259. # Looks good. Validate the session
  2260. self.db_pool.simple_update_txn(
  2261. txn,
  2262. table="threepid_validation_session",
  2263. keyvalues={"session_id": session_id},
  2264. updatevalues={"validated_at": self._clock.time_msec()},
  2265. )
  2266. return next_link
  2267. # Return next_link if it exists
  2268. return await self.db_pool.runInteraction(
  2269. "validate_threepid_session_txn", validate_threepid_session_txn
  2270. )
  2271. async def start_or_continue_validation_session(
  2272. self,
  2273. medium: str,
  2274. address: str,
  2275. session_id: str,
  2276. client_secret: str,
  2277. send_attempt: int,
  2278. next_link: Optional[str],
  2279. token: str,
  2280. token_expires: int,
  2281. ) -> None:
  2282. """Creates a new threepid validation session if it does not already
  2283. exist and associates a new validation token with it
  2284. Args:
  2285. medium: The medium of the 3PID
  2286. address: The address of the 3PID
  2287. session_id: The id of this validation session
  2288. client_secret: A unique string provided by the client to help
  2289. identify this validation attempt
  2290. send_attempt: The latest send_attempt on this session
  2291. next_link: The link to redirect the user to upon successful validation
  2292. token: The validation token
  2293. token_expires: The timestamp for which after the token will no
  2294. longer be valid
  2295. """
  2296. def start_or_continue_validation_session_txn(txn: LoggingTransaction) -> None:
  2297. # Create or update a validation session
  2298. self.db_pool.simple_upsert_txn(
  2299. txn,
  2300. table="threepid_validation_session",
  2301. keyvalues={"session_id": session_id},
  2302. values={"last_send_attempt": send_attempt},
  2303. insertion_values={
  2304. "medium": medium,
  2305. "address": address,
  2306. "client_secret": client_secret,
  2307. },
  2308. )
  2309. # Create a new validation token with this session ID
  2310. self.db_pool.simple_insert_txn(
  2311. txn,
  2312. table="threepid_validation_token",
  2313. values={
  2314. "session_id": session_id,
  2315. "token": token,
  2316. "next_link": next_link,
  2317. "expires": token_expires,
  2318. },
  2319. )
  2320. await self.db_pool.runInteraction(
  2321. "start_or_continue_validation_session",
  2322. start_or_continue_validation_session_txn,
  2323. )
  2324. async def update_user_approval_status(
  2325. self, user_id: UserID, approved: bool
  2326. ) -> None:
  2327. """Set the user's 'approved' flag to the given value.
  2328. The boolean will be turned into an int (in update_user_approval_status_txn)
  2329. because the column is a smallint.
  2330. Args:
  2331. user_id: the user to update the flag for.
  2332. approved: the value to set the flag to.
  2333. """
  2334. await self.db_pool.runInteraction(
  2335. "update_user_approval_status",
  2336. self.update_user_approval_status_txn,
  2337. user_id.to_string(),
  2338. approved,
  2339. )
  2340. @wrap_as_background_process("delete_expired_login_tokens")
  2341. async def _delete_expired_login_tokens(self) -> None:
  2342. """Remove login tokens with expiry dates that have passed."""
  2343. def _delete_expired_login_tokens_txn(txn: LoggingTransaction, ts: int) -> None:
  2344. sql = "DELETE FROM login_tokens WHERE expiry_ts <= ?"
  2345. txn.execute(sql, (ts,))
  2346. # We keep the expired tokens for an extra 5 minutes so we can measure how many
  2347. # times a token is being used after its expiry
  2348. now = self._clock.time_msec()
  2349. await self.db_pool.runInteraction(
  2350. "delete_expired_login_tokens",
  2351. _delete_expired_login_tokens_txn,
  2352. now - (5 * 60 * 1000),
  2353. )
  2354. def find_max_generated_user_id_localpart(cur: Cursor) -> int:
  2355. """
  2356. Gets the localpart of the max current generated user ID.
  2357. Generated user IDs are integers, so we find the largest integer user ID
  2358. already taken and return that.
  2359. """
  2360. # We bound between '@0' and '@a' to avoid pulling the entire table
  2361. # out.
  2362. cur.execute("SELECT name FROM users WHERE '@0' <= name AND name < '@a'")
  2363. regex = re.compile(r"^@(\d+):")
  2364. max_found = 0
  2365. for (user_id,) in cur:
  2366. match = regex.search(user_id)
  2367. if match:
  2368. max_found = max(int(match.group(1)), max_found)
  2369. return max_found