diff --git a/synapse/storage/registration.py b/synapse/storage/registration.py
index 4cf159ba81..e4aa386a2d 100644
--- a/synapse/storage/registration.py
+++ b/synapse/storage/registration.py
@@ -17,12 +17,13 @@
import re
+from six import iterkeys
from six.moves import range
from twisted.internet import defer
from synapse.api.constants import UserTypes
-from synapse.api.errors import Codes, StoreError
+from synapse.api.errors import Codes, StoreError, ThreepidValidationError
from synapse.storage import background_updates
from synapse.storage._base import SQLBaseStore
from synapse.types import UserID
@@ -422,7 +423,7 @@ class RegistrationWorkerStore(SQLBaseStore):
defer.returnValue(None)
@defer.inlineCallbacks
- def get_user_id_by_threepid(self, medium, address):
+ def get_user_id_by_threepid(self, medium, address, require_verified=False):
"""Returns user id from threepid
Args:
@@ -963,7 +964,6 @@ class RegistrationStore(
We do this by grandfathering in existing user threepids assuming that
they used one of the server configured trusted identity servers.
"""
-
id_servers = set(self.config.trusted_third_party_id_servers)
def _bg_user_threepids_grandfather_txn(txn):
@@ -984,3 +984,256 @@ class RegistrationStore(
yield self._end_background_update("user_threepids_grandfather")
defer.returnValue(1)
+
+ def get_threepid_validation_session(
+ self,
+ medium,
+ client_secret,
+ address=None,
+ sid=None,
+ validated=None,
+ ):
+ """Gets a session_id and last_send_attempt (if available) for a
+ client_secret/medium/(address|session_id) combo
+
+ Args:
+ medium (str): The medium of the 3PID
+ address (str): The address of the 3PID
+ sid (str): The ID of the validation session
+ client_secret (str): A unique string provided by the client to
+ help identify this validation attempt
+ validated (bool|None): Whether sessions should be filtered by
+ whether they have been validated already or not. None to
+ perform no filtering
+
+ Returns:
+ deferred {str, int}|None: A dict containing the
+ latest session_id and send_attempt count for this 3PID.
+ Otherwise None if there hasn't been a previous attempt
+ """
+ keyvalues = {
+ "medium": medium,
+ "client_secret": client_secret,
+ }
+
+ if sid:
+ keyvalues["session_id"] = sid
+ elif address:
+ keyvalues["address"] = address
+ else:
+ raise StoreError(500, "Either address or sid must be provided")
+
+ def get_threepid_validation_session_txn(txn):
+ cols_to_return = [
+ "session_id", "medium", "address",
+ "client_secret", "last_send_attempt", "validated_at",
+ ]
+
+ sql = "SELECT %s FROM threepid_validation_session" % ", ".join(cols_to_return)
+ sql += " WHERE %s" % " AND ".join("%s = ?" % k for k in iterkeys(keyvalues))
+
+ if validated is not None:
+ sql += " AND validated_at IS " + ("NOT NULL" if validated else "NULL")
+
+ sql += " LIMIT 1"
+
+ txn.execute(sql, list(keyvalues.values()))
+ row = txn.fetchone()
+
+ if not row:
+ return None
+
+ # Convert the resulting row to a dictionary
+ ret = {}
+ for i in range(len(cols_to_return)):
+ ret[cols_to_return[i]] = row[i]
+
+ return ret
+
+ return self.runInteraction(
+ "get_threepid_validation_session",
+ get_threepid_validation_session_txn,
+ )
+
+ @defer.inlineCallbacks
+ def validate_threepid_session(
+ self,
+ session_id,
+ client_secret,
+ token,
+ current_ts,
+ ):
+ """Attempt to validate a threepid session using a token
+
+ Args:
+ session_id (str): The id of a validation session
+ client_secret (str): A unique string provided by the client to
+ help identify this validation attempt
+ token (str): A validation token
+ current_ts (int): The current unix time in milliseconds. Used for
+ checking token expiry status
+
+ Returns:
+ deferred str|None: A str representing a link to redirect the user
+ to if there is one.
+ """
+ row = yield self._simple_select_one(
+ table="threepid_validation_session",
+ keyvalues={"session_id": session_id},
+ retcols=["client_secret", "validated_at"],
+ desc="validate_threepid_session_select_session",
+ allow_none=True,
+ )
+
+ if not row:
+ raise ThreepidValidationError(400, "Unknown session_id")
+ retrieved_client_secret = row["client_secret"]
+ validated_at = row["validated_at"]
+
+ if validated_at:
+ raise ThreepidValidationError(
+ 400, "This session has already been validated",
+ )
+ if retrieved_client_secret != client_secret:
+ raise ThreepidValidationError(
+ 400, "This client_secret does not match the provided session_id",
+ )
+
+ row = yield self._simple_select_one(
+ table="threepid_validation_token",
+ keyvalues={"session_id": session_id, "token": token},
+ retcols=["expires", "next_link"],
+ desc="validate_threepid_session_select_token",
+ allow_none=True,
+ )
+
+ if not row:
+ raise ThreepidValidationError(
+ 400, "Validation token not found or has expired",
+ )
+ expires = row["expires"]
+ next_link = row["next_link"]
+
+ if expires <= current_ts:
+ raise ThreepidValidationError(
+ 400, "This token has expired. Please request a new one",
+ )
+
+ # Looks good. Validate the session
+ yield self._simple_update(
+ table="threepid_validation_session",
+ keyvalues={"session_id": session_id},
+ updatevalues={"validated_at": self.clock.time_msec()},
+ desc="validate_threepid_session_update",
+ )
+
+ # Return next_link if it exists
+ defer.returnValue(next_link)
+
+ def upsert_threepid_validation_session(
+ self,
+ medium,
+ address,
+ client_secret,
+ send_attempt,
+ session_id,
+ validated_at=None,
+ ):
+ """Upsert a threepid validation session
+
+ Args:
+ medium (str): The medium of the 3PID
+ address (str): The address of the 3PID
+ client_secret (str): A unique string provided by the client to
+ help identify this validation attempt
+ session_id (str): The id of this validation session
+ validated_at (int): The unix timestamp in milliseconds of when
+ the session was marked as valid
+ """
+ insertion_values = {
+ "medium": medium,
+ "address": address,
+ "client_secret": client_secret,
+ }
+
+ if validated_at:
+ insertion_values["validated_at"] = validated_at
+
+ return self._simple_upsert(
+ table="threepid_validation_session",
+ keyvalues={"session_id": session_id},
+ values={"last_send_attempt": send_attempt},
+ insertion_values=insertion_values,
+ desc="upsert_threepid_validation_session",
+ )
+
+ def insert_threepid_validation_token(
+ self,
+ session_id,
+ token,
+ next_link,
+ expires,
+ ):
+ """Insert a new 3PID validation token and details
+
+ Args:
+ session_id (str): The id of the validation session this attempt
+ is related to
+ token (str): The validation token
+ expires (int): The timestamp for which after this token will no
+ longer be valid
+ """
+ return self._simple_insert(
+ table="threepid_validation_token",
+ values={
+ "session_id": session_id,
+ "token": token,
+ "next_link": next_link,
+ "expires": expires,
+ },
+ desc="insert_threepid_validation_token",
+ )
+
+ def cull_expired_threepid_validation_tokens(self):
+ """Remove threepid validation tokens with expiry dates that have passed"""
+ def cull_expired_threepid_validation_tokens_txn(txn, ts):
+ sql = """
+ DELETE FROM threepid_validation_token WHERE
+ expires < ?
+ """
+
+ return txn.execute(sql, (ts,))
+
+ return self.runInteraction(
+ "cull_expired_threepid_validation_tokens",
+ cull_expired_threepid_validation_tokens_txn,
+ self.clock.time_msec(),
+ )
+
+ def delete_threepid_session(self, session_id):
+ """Removes a threepid validation session from the database. This can
+ be done after validation has been performed and whatever action was
+ waiting on it has been carried out
+
+ Args:
+ session_id (str): The ID of the session to delete
+ """
+ return self._simple_delete(
+ table="threepid_validation_session",
+ keyvalues={"session_id": session_id},
+ desc="delete_threepid_session",
+ )
+
+ def delete_threepid_tokens(self, session_id):
+ """Removes threepid validation tokens from the database which match a
+ given session ID.
+
+ Args:
+ session_id (str): The ID of the session to delete
+ """
+ # Delete tokens associated with this session id
+ return self._simple_delete_one(
+ table="threepid_validation_token",
+ keyvalues={"session_id": session_id},
+ desc="delete_threepid_session_tokens",
+ )
|