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

Commit

Permalink
Implement MSC2290 (#6043)
Browse files Browse the repository at this point in the history
Implements MSC2290. This PR adds two new endpoints, /unstable/account/3pid/add and /unstable/account/3pid/bind. Depending on the progress of that MSC the unstable prefix may go away.

This PR also removes the blacklist on some 3PID tests which occurs in #6042, as the corresponding Sytest PR changes them to use the new endpoints.

Finally, it also modifies the account deactivation code such that it doesn't just try to deactivate 3PIDs that were bound to the user's account, but any 3PIDs that were bound through the homeserver on that user's account.
  • Loading branch information
anoadragon453 authored and richvdh committed Sep 23, 2019
1 parent 1b519e0 commit 30af161
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 134 deletions.
1 change: 1 addition & 0 deletions changelog.d/6043.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implement new Client Server API endpoints `/account/3pid/add` and `/account/3pid/bind` as per [MSC2290](https://github.com/matrix-org/matrix-doc/pull/2290).
4 changes: 3 additions & 1 deletion synapse/handlers/deactivate_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ def deactivate_account(self, user_id, erase_data, id_server=None):
# unbinding
identity_server_supports_unbinding = True

threepids = yield self.store.user_get_threepids(user_id)
# Retrieve the 3PIDs this user has bound to an identity server
threepids = yield self.store.user_get_bound_threepids(user_id)

for threepid in threepids:
try:
result = yield self._identity_handler.try_unbind_threepid(
Expand Down
134 changes: 83 additions & 51 deletions synapse/handlers/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
HttpResponseException,
SynapseError,
)
from synapse.config.emailconfig import ThreepidBehaviour
from synapse.util.stringutils import random_string

from ._base import BaseHandler
Expand All @@ -45,36 +46,6 @@ def __init__(self, hs):
self.federation_http_client = hs.get_http_client()
self.hs = hs

def _extract_items_from_creds_dict(self, creds):
"""
Retrieve entries from a "credentials" dictionary
Args:
creds (dict[str, str]): Dictionary of credentials that contain the following keys:
* client_secret|clientSecret: A unique secret str provided by the client
* id_server|idServer: the domain of the identity server to query
* id_access_token: The access token to authenticate to the identity
server with.
Returns:
tuple(str, str, str|None): A tuple containing the client_secret, the id_server,
and the id_access_token value if available.
"""
client_secret = creds.get("client_secret") or creds.get("clientSecret")
if not client_secret:
raise SynapseError(
400, "No client_secret in creds", errcode=Codes.MISSING_PARAM
)

id_server = creds.get("id_server") or creds.get("idServer")
if not id_server:
raise SynapseError(
400, "No id_server in creds", errcode=Codes.MISSING_PARAM
)

id_access_token = creds.get("id_access_token")
return client_secret, id_server, id_access_token

@defer.inlineCallbacks
def threepid_from_creds(self, id_server, creds):
"""
Expand Down Expand Up @@ -113,35 +84,50 @@ def threepid_from_creds(self, id_server, creds):
data = yield self.http_client.get_json(url, query_params)
except TimeoutError:
raise SynapseError(500, "Timed out contacting identity server")
return data if "medium" in data else None
except HttpResponseException as e:
logger.info(
"%s returned %i for threepid validation for: %s",
id_server,
e.code,
creds,
)
return None

# Old versions of Sydent return a 200 http code even on a failed validation
# check. Thus, in addition to the HttpResponseException check above (which
# checks for non-200 errors), we need to make sure validation_session isn't
# actually an error, identified by the absence of a "medium" key
# See https://github.com/matrix-org/sydent/issues/215 for details
if "medium" in data:
return data

logger.info("%s reported non-validated threepid: %s", id_server, creds)
return None

@defer.inlineCallbacks
def bind_threepid(self, creds, mxid, use_v2=True):
def bind_threepid(
self, client_secret, sid, mxid, id_server, id_access_token=None, use_v2=True
):
"""Bind a 3PID to an identity server
Args:
creds (dict[str, str]): Dictionary of credentials that contain the following keys:
* client_secret|clientSecret: A unique secret str provided by the client
* id_server|idServer: the domain of the identity server to query
* id_access_token: The access token to authenticate to the identity
server with. Required if use_v2 is true
client_secret (str): A unique secret provided by the client
sid (str): The ID of the validation session
mxid (str): The MXID to bind the 3PID to
use_v2 (bool): Whether to use v2 Identity Service API endpoints
id_server (str): The domain of the identity server to query
id_access_token (str): The access token to authenticate to the identity
server with, if necessary. Required if use_v2 is true
use_v2 (bool): Whether to use v2 Identity Service API endpoints. Defaults to True
Returns:
Deferred[dict]: The response from the identity server
"""
logger.debug("binding threepid %r to %s", creds, mxid)

client_secret, id_server, id_access_token = self._extract_items_from_creds_dict(
creds
)

sid = creds.get("sid")
if not sid:
raise SynapseError(
400, "No sid in three_pid_creds", errcode=Codes.MISSING_PARAM
)
logger.debug("Proxying threepid bind request for %s to %s", mxid, id_server)

# If an id_access_token is not supplied, force usage of v1
if id_access_token is None:
Expand All @@ -160,7 +146,6 @@ def bind_threepid(self, creds, mxid, use_v2=True):
data = yield self.http_client.post_json_get_json(
bind_url, bind_data, headers=headers
)
logger.debug("bound threepid %r to %s", creds, mxid)

# Remember where we bound the threepid
yield self.store.add_user_bound_threepid(
Expand All @@ -182,7 +167,10 @@ def bind_threepid(self, creds, mxid, use_v2=True):
return data

logger.info("Got 404 when POSTing JSON %s, falling back to v1 URL", bind_url)
return (yield self.bind_threepid(creds, mxid, use_v2=False))
res = yield self.bind_threepid(
client_secret, sid, mxid, id_server, id_access_token, use_v2=False
)
return res

@defer.inlineCallbacks
def try_unbind_threepid(self, mxid, threepid):
Expand Down Expand Up @@ -459,6 +447,50 @@ def requestMsisdnToken(
except TimeoutError:
raise SynapseError(500, "Timed out contacting identity server")

@defer.inlineCallbacks
def validate_threepid_session(self, client_secret, sid):
"""Validates a threepid session with only the client secret and session ID
Tries validating against any configured account_threepid_delegates as well as locally.
Args:
client_secret (str): A secret provided by the client
sid (str): The ID of the session
Returns:
Dict[str, str|int] if validation was successful, otherwise None
"""
# XXX: We shouldn't need to keep wrapping and unwrapping this value
threepid_creds = {"client_secret": client_secret, "sid": sid}

# We don't actually know which medium this 3PID is. Thus we first assume it's email,
# and if validation fails we try msisdn
validation_session = None

# Try to validate as email
if self.hs.config.threepid_behaviour_email == ThreepidBehaviour.REMOTE:
# Ask our delegated email identity server
validation_session = yield self.threepid_from_creds(
self.hs.config.account_threepid_delegate_email, threepid_creds
)
elif self.hs.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
# Get a validated session matching these details
validation_session = yield self.store.get_threepid_validation_session(
"email", client_secret, sid=sid, validated=True
)

if validation_session:
return validation_session

# Try to validate as msisdn
if self.hs.config.account_threepid_delegate_msisdn:
# Ask our delegated msisdn identity server
validation_session = yield self.threepid_from_creds(
self.hs.config.account_threepid_delegate_msisdn, threepid_creds
)

return validation_session


def create_id_access_token_header(id_access_token):
"""Create an Authorization header for passing to SimpleHttpClient as the header value
Expand Down
Loading

0 comments on commit 30af161

Please sign in to comment.