mirror of
https://git.anonymousland.org/anonymousland/synapse.git
synced 2025-05-02 12:46:01 -04:00
Merge remote-tracking branch 'origin/develop' into rav/saml_mapping_work
This commit is contained in:
commit
ed8b92f0d2
51 changed files with 844 additions and 235 deletions
|
@ -73,7 +73,9 @@ class DeactivateAccountHandler(BaseHandler):
|
|||
# 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(
|
||||
|
|
|
@ -22,6 +22,7 @@ import logging
|
|||
from canonicaljson import json
|
||||
|
||||
from twisted.internet import defer
|
||||
from twisted.internet.error import TimeoutError
|
||||
|
||||
from synapse.api.errors import (
|
||||
CodeMessageException,
|
||||
|
@ -29,6 +30,8 @@ from synapse.api.errors import (
|
|||
HttpResponseException,
|
||||
SynapseError,
|
||||
)
|
||||
from synapse.config.emailconfig import ThreepidBehaviour
|
||||
from synapse.http.client import SimpleHttpClient
|
||||
from synapse.util.stringutils import random_string
|
||||
|
||||
from ._base import BaseHandler
|
||||
|
@ -40,40 +43,15 @@ class IdentityHandler(BaseHandler):
|
|||
def __init__(self, hs):
|
||||
super(IdentityHandler, self).__init__(hs)
|
||||
|
||||
self.http_client = hs.get_simple_http_client()
|
||||
self.http_client = SimpleHttpClient(hs)
|
||||
# We create a blacklisting instance of SimpleHttpClient for contacting identity
|
||||
# servers specified by clients
|
||||
self.blacklisting_http_client = SimpleHttpClient(
|
||||
hs, ip_blacklist=hs.config.federation_ip_range_blacklist
|
||||
)
|
||||
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):
|
||||
"""
|
||||
|
@ -81,11 +59,10 @@ class IdentityHandler(BaseHandler):
|
|||
given identity server
|
||||
|
||||
Args:
|
||||
id_server (str|None): The identity server to validate 3PIDs against. If None,
|
||||
we will attempt to extract id_server creds
|
||||
id_server (str): The identity server to validate 3PIDs against. Must be a
|
||||
complete URL including the protocol (http(s)://)
|
||||
|
||||
creds (dict[str, str]): Dictionary containing the following keys:
|
||||
* id_server|idServer: An optional domain name of an identity server
|
||||
* client_secret|clientSecret: A unique secret str provided by the client
|
||||
* sid: The ID of the validation session
|
||||
|
||||
|
@ -104,51 +81,59 @@ class IdentityHandler(BaseHandler):
|
|||
raise SynapseError(
|
||||
400, "Missing param session_id in creds", errcode=Codes.MISSING_PARAM
|
||||
)
|
||||
if not id_server:
|
||||
# Attempt to get the id_server from the creds dict
|
||||
id_server = creds.get("id_server") or creds.get("idServer")
|
||||
if not id_server:
|
||||
raise SynapseError(
|
||||
400, "Missing param id_server in creds", errcode=Codes.MISSING_PARAM
|
||||
)
|
||||
|
||||
query_params = {"sid": session_id, "client_secret": client_secret}
|
||||
|
||||
url = "https://%s%s" % (
|
||||
id_server,
|
||||
"/_matrix/identity/api/v1/3pid/getValidated3pid",
|
||||
)
|
||||
url = id_server + "/_matrix/identity/api/v1/3pid/getValidated3pid"
|
||||
|
||||
data = yield self.http_client.get_json(url, query_params)
|
||||
return data if "medium" in data else None
|
||||
try:
|
||||
data = yield self.http_client.get_json(url, query_params)
|
||||
except TimeoutError:
|
||||
raise SynapseError(500, "Timed out contacting identity server")
|
||||
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:
|
||||
|
@ -164,10 +149,11 @@ class IdentityHandler(BaseHandler):
|
|||
bind_url = "https://%s/_matrix/identity/api/v1/3pid/bind" % (id_server,)
|
||||
|
||||
try:
|
||||
data = yield self.http_client.post_json_get_json(
|
||||
# Use the blacklisting http client as this call is only to identity servers
|
||||
# provided by a client
|
||||
data = yield self.blacklisting_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(
|
||||
|
@ -182,12 +168,17 @@ class IdentityHandler(BaseHandler):
|
|||
if e.code != 404 or not use_v2:
|
||||
logger.error("3PID bind failed with Matrix error: %r", e)
|
||||
raise e.to_synapse_error()
|
||||
except TimeoutError:
|
||||
raise SynapseError(500, "Timed out contacting identity server")
|
||||
except CodeMessageException as e:
|
||||
data = json.loads(e.msg) # XXX WAT?
|
||||
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):
|
||||
|
@ -263,7 +254,11 @@ class IdentityHandler(BaseHandler):
|
|||
headers = {b"Authorization": auth_headers}
|
||||
|
||||
try:
|
||||
yield self.http_client.post_json_get_json(url, content, headers)
|
||||
# Use the blacklisting http client as this call is only to identity servers
|
||||
# provided by a client
|
||||
yield self.blacklisting_http_client.post_json_get_json(
|
||||
url, content, headers
|
||||
)
|
||||
changed = True
|
||||
except HttpResponseException as e:
|
||||
changed = False
|
||||
|
@ -272,7 +267,9 @@ class IdentityHandler(BaseHandler):
|
|||
logger.warn("Received %d response while unbinding threepid", e.code)
|
||||
else:
|
||||
logger.error("Failed to unbind threepid on identity server: %s", e)
|
||||
raise SynapseError(502, "Failed to contact identity server")
|
||||
raise SynapseError(500, "Failed to contact identity server")
|
||||
except TimeoutError:
|
||||
raise SynapseError(500, "Timed out contacting identity server")
|
||||
|
||||
yield self.store.remove_user_bound_threepid(
|
||||
user_id=mxid,
|
||||
|
@ -405,6 +402,8 @@ class IdentityHandler(BaseHandler):
|
|||
except HttpResponseException as e:
|
||||
logger.info("Proxied requestToken failed: %r", e)
|
||||
raise e.to_synapse_error()
|
||||
except TimeoutError:
|
||||
raise SynapseError(500, "Timed out contacting identity server")
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def requestMsisdnToken(
|
||||
|
@ -453,10 +452,100 @@ class IdentityHandler(BaseHandler):
|
|||
id_server + "/_matrix/identity/api/v1/validate/msisdn/requestToken",
|
||||
params,
|
||||
)
|
||||
return data
|
||||
except HttpResponseException as e:
|
||||
logger.info("Proxied requestToken failed: %r", e)
|
||||
raise e.to_synapse_error()
|
||||
except TimeoutError:
|
||||
raise SynapseError(500, "Timed out contacting identity server")
|
||||
|
||||
assert self.hs.config.public_baseurl
|
||||
|
||||
# we need to tell the client to send the token back to us, since it doesn't
|
||||
# otherwise know where to send it, so add submit_url response parameter
|
||||
# (see also MSC2078)
|
||||
data["submit_url"] = (
|
||||
self.hs.config.public_baseurl
|
||||
+ "_matrix/client/unstable/add_threepid/msisdn/submit_token"
|
||||
)
|
||||
return data
|
||||
|
||||
@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
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def proxy_msisdn_submit_token(self, id_server, client_secret, sid, token):
|
||||
"""Proxy a POST submitToken request to an identity server for verification purposes
|
||||
|
||||
Args:
|
||||
id_server (str): The identity server URL to contact
|
||||
|
||||
client_secret (str): Secret provided by the client
|
||||
|
||||
sid (str): The ID of the session
|
||||
|
||||
token (str): The verification token
|
||||
|
||||
Raises:
|
||||
SynapseError: If we failed to contact the identity server
|
||||
|
||||
Returns:
|
||||
Deferred[dict]: The response dict from the identity server
|
||||
"""
|
||||
body = {"client_secret": client_secret, "sid": sid, "token": token}
|
||||
|
||||
try:
|
||||
return (
|
||||
yield self.http_client.post_json_get_json(
|
||||
id_server + "/_matrix/identity/api/v1/validate/msisdn/submitToken",
|
||||
body,
|
||||
)
|
||||
)
|
||||
except TimeoutError:
|
||||
raise SynapseError(500, "Timed out contacting identity server")
|
||||
except HttpResponseException as e:
|
||||
logger.warning("Error contacting msisdn account_threepid_delegate: %s", e)
|
||||
raise SynapseError(400, "Error contacting the identity server")
|
||||
|
||||
|
||||
def create_id_access_token_header(id_access_token):
|
||||
|
|
|
@ -25,11 +25,13 @@ from signedjson.sign import verify_signed_json
|
|||
from unpaddedbase64 import decode_base64
|
||||
|
||||
from twisted.internet import defer
|
||||
from twisted.internet.error import TimeoutError
|
||||
|
||||
from synapse import types
|
||||
from synapse.api.constants import EventTypes, Membership
|
||||
from synapse.api.errors import AuthError, Codes, HttpResponseException, SynapseError
|
||||
from synapse.handlers.identity import LookupAlgorithm, create_id_access_token_header
|
||||
from synapse.http.client import SimpleHttpClient
|
||||
from synapse.types import RoomID, UserID
|
||||
from synapse.util.async_helpers import Linearizer
|
||||
from synapse.util.distributor import user_joined_room, user_left_room
|
||||
|
@ -61,7 +63,11 @@ class RoomMemberHandler(object):
|
|||
self.auth = hs.get_auth()
|
||||
self.state_handler = hs.get_state_handler()
|
||||
self.config = hs.config
|
||||
self.simple_http_client = hs.get_simple_http_client()
|
||||
# We create a blacklisting instance of SimpleHttpClient for contacting identity
|
||||
# servers specified by clients
|
||||
self.simple_http_client = SimpleHttpClient(
|
||||
hs, ip_blacklist=hs.config.federation_ip_range_blacklist
|
||||
)
|
||||
|
||||
self.federation_handler = hs.get_handlers().federation_handler
|
||||
self.directory_handler = hs.get_handlers().directory_handler
|
||||
|
@ -756,7 +762,8 @@ class RoomMemberHandler(object):
|
|||
raise AuthError(401, "No signatures on 3pid binding")
|
||||
yield self._verify_any_signature(data, id_server)
|
||||
return data["mxid"]
|
||||
|
||||
except TimeoutError:
|
||||
raise SynapseError(500, "Timed out contacting identity server")
|
||||
except IOError as e:
|
||||
logger.warning("Error from v1 identity server lookup: %s" % (e,))
|
||||
|
||||
|
@ -777,10 +784,13 @@ class RoomMemberHandler(object):
|
|||
Deferred[str|None]: the matrix ID of the 3pid, or None if it is not recognised.
|
||||
"""
|
||||
# Check what hashing details are supported by this identity server
|
||||
hash_details = yield self.simple_http_client.get_json(
|
||||
"%s%s/_matrix/identity/v2/hash_details" % (id_server_scheme, id_server),
|
||||
{"access_token": id_access_token},
|
||||
)
|
||||
try:
|
||||
hash_details = yield self.simple_http_client.get_json(
|
||||
"%s%s/_matrix/identity/v2/hash_details" % (id_server_scheme, id_server),
|
||||
{"access_token": id_access_token},
|
||||
)
|
||||
except TimeoutError:
|
||||
raise SynapseError(500, "Timed out contacting identity server")
|
||||
|
||||
if not isinstance(hash_details, dict):
|
||||
logger.warning(
|
||||
|
@ -851,6 +861,8 @@ class RoomMemberHandler(object):
|
|||
},
|
||||
headers=headers,
|
||||
)
|
||||
except TimeoutError:
|
||||
raise SynapseError(500, "Timed out contacting identity server")
|
||||
except Exception as e:
|
||||
logger.warning("Error when performing a v2 3pid lookup: %s", e)
|
||||
raise SynapseError(
|
||||
|
@ -873,10 +885,13 @@ class RoomMemberHandler(object):
|
|||
if server_hostname not in data["signatures"]:
|
||||
raise AuthError(401, "No signature from server %s" % (server_hostname,))
|
||||
for key_name, signature in data["signatures"][server_hostname].items():
|
||||
key_data = yield self.simple_http_client.get_json(
|
||||
"%s%s/_matrix/identity/api/v1/pubkey/%s"
|
||||
% (id_server_scheme, server_hostname, key_name)
|
||||
)
|
||||
try:
|
||||
key_data = yield self.simple_http_client.get_json(
|
||||
"%s%s/_matrix/identity/api/v1/pubkey/%s"
|
||||
% (id_server_scheme, server_hostname, key_name)
|
||||
)
|
||||
except TimeoutError:
|
||||
raise SynapseError(500, "Timed out contacting identity server")
|
||||
if "public_key" not in key_data:
|
||||
raise AuthError(
|
||||
401, "No public key named %s from %s" % (key_name, server_hostname)
|
||||
|
@ -1051,6 +1066,8 @@ class RoomMemberHandler(object):
|
|||
invite_config,
|
||||
{"Authorization": create_id_access_token_header(id_access_token)},
|
||||
)
|
||||
except TimeoutError:
|
||||
raise SynapseError(500, "Timed out contacting identity server")
|
||||
except HttpResponseException as e:
|
||||
if e.code != 404:
|
||||
logger.info("Failed to POST %s with JSON: %s", url, e)
|
||||
|
@ -1067,6 +1084,8 @@ class RoomMemberHandler(object):
|
|||
data = yield self.simple_http_client.post_json_get_json(
|
||||
url, invite_config
|
||||
)
|
||||
except TimeoutError:
|
||||
raise SynapseError(500, "Timed out contacting identity server")
|
||||
except HttpResponseException as e:
|
||||
logger.warning(
|
||||
"Error trying to call /store-invite on %s%s: %s",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue