def validateSessionWithToken(self, sid, clientSecret, token): valSessionStore = ThreePidValSessionStore(self.sydent) s = valSessionStore.getTokenSessionById(sid) if not s: logger.info("Session ID %s not found", (sid)) return False if not clientSecret == s.clientSecret: logger.info("Incorrect client secret", (sid)) raise IncorrectClientSecretException() if s.mtime + ValidationSession.THREEPID_SESSION_VALIDATION_TIMEOUT < time_msec(): logger.info("Session expired") raise SessionExpiredException() # TODO once we can validate the token oob #if tokenObj.validated and clientSecret == tokenObj.clientSecret: # return True if s.token == token: logger.info("Setting session %s as validated", (s.id)) valSessionStore.setValidated(s.id, True) return {'success': True} else: logger.info("Incorrect token submitted") return False
def render_GET(self, request): err, args = get_args(request, ('sid', 'client_secret')) if err: return err sid = args['sid'] clientSecret = args['client_secret'] valSessionStore = ThreePidValSessionStore(self.sydent) noMatchError = {'errcode': 'M_NO_VALID_SESSION', 'error': "No valid session was found matching that sid and client secret"} try: s = valSessionStore.getValidatedSession(sid, clientSecret) except IncorrectClientSecretException: return noMatchError except SessionExpiredException: return {'errcode': 'M_SESSION_EXPIRED', 'error': "This validation session has expired: call requestToken again"} except InvalidSessionIdException: return noMatchError except SessionNotValidatedException: return {'errcode': 'M_SESSION_NOT_VALIDATED', 'error': "This validation session has not yet been completed"} return { 'medium': s.medium, 'address': s.address, 'validated_at': s.mtime }
def render_POST(self, request): send_cors(request) err, args = get_args(request, ('sid', 'client_secret', 'mxid')) if err: return err sid = args['sid'] mxid = args['mxid'] clientSecret = args['client_secret'] # Return the same error for not found / bad client secret otherwise people can get information about # sessions without knowing the secret noMatchError = {'errcode': 'M_NO_VALID_SESSION', 'error': "No valid session was found matching that sid and client secret"} try: valSessionStore = ThreePidValSessionStore(self.sydent) s = valSessionStore.getValidatedSession(sid, clientSecret) except IncorrectClientSecretException: return noMatchError except SessionExpiredException: return {'errcode': 'M_SESSION_EXPIRED', 'error': "This validation session has expired: call requestToken again"} except InvalidSessionIdException: return noMatchError except SessionNotValidatedException: return {'errcode': 'M_SESSION_NOT_VALIDATED', 'error': "This validation session has not yet been completed"} res = self.sydent.threepidBinder.addBinding(s.medium, s.address, mxid) return res
def validateSessionWithToken(sydent, sid, clientSecret, token): """ Attempt to validate a session, identified by the sid, using the token from out-of-band. The client secret is given to prevent attempts to guess the token for a sid. If the session was sucessfully validated, return a dict with 'success': True that can be sent to the client, otherwise return False. """ valSessionStore = ThreePidValSessionStore(sydent) s = valSessionStore.getTokenSessionById(sid) if not s: logger.info("Session ID %s not found", (sid)) return False if not clientSecret == s.clientSecret: logger.info("Incorrect client secret", (sid)) raise IncorrectClientSecretException() if s.mtime + ValidationSession.THREEPID_SESSION_VALIDATION_TIMEOUT_MS < time_msec(): logger.info("Session expired") raise SessionExpiredException() # TODO once we can validate the token oob #if tokenObj.validated and clientSecret == tokenObj.clientSecret: # return True if s.token == token: logger.info("Setting session %s as validated", (s.id)) valSessionStore.setValidated(s.id, True) return {'success': True} else: logger.info("Incorrect token submitted") return False
def addBinding(self, valSessionId, clientSecret, mxid): valSessionStore = ThreePidValSessionStore(self.sydent) localAssocStore = LocalAssociationStore(self.sydent) s = valSessionStore.getValidatedSession(valSessionId, clientSecret) createdAt = time_msec() expires = createdAt + ThreepidBinder.THREEPID_ASSOCIATION_LIFETIME_MS assoc = ThreepidAssociation(s.medium, s.address, mxid, createdAt, createdAt, expires) localAssocStore.addOrUpdateAssociation(assoc) self.sydent.pusher.doLocalPush() assocSigner = AssociationSigner(self.sydent) sgassoc = assocSigner.signedThreePidAssociation(assoc) return sgassoc
def requestToken(self, emailAddress, clientSecret, sendAttempt, nextLink, ipaddress=None): valSessionStore = ThreePidValSessionStore(self.sydent) valSession = valSessionStore.getOrCreateTokenSession(medium='email', address=emailAddress, clientSecret=clientSecret) valSessionStore.setMtime(valSession.id, time_msec()) if int(valSession.sendAttemptNumber) >= int(sendAttempt): logger.info("Not mailing code because current send attempt (%d) is not less than given send attempt (%s)", int(sendAttempt), int(valSession.sendAttemptNumber)) return valSession.id ipstring = ipaddress if ipaddress else u"an unknown location" substitutions = { 'ipaddress': ipstring, 'link': self.makeValidateLink(valSession, clientSecret, nextLink), 'token': valSession.token, } logger.info( "Attempting to mail code %s (nextLink: %s) to %s", valSession.token, nextLink, emailAddress, ) sendEmail(self.sydent, 'email.template', emailAddress, substitutions) valSessionStore.setSendAttemptNumber(valSession.id, sendAttempt) return valSession.id
def render_GET(self, request): # err = require_args(request, ('sid', 'client_secret')) err = require_args(request, ('sid',)) if err: return err sid = request.args['sid'][0] #clientSecret = request.args['client_secret'][0] if 'client_secret' in request.args: clientSecret = request.args['client_secret'][0] elif 'clientSecret' in request.args: clientSecret = request.args['clientSecret'][0] else: request.setResponseCode(400) return {'errcode': 'M_MISSING_PARAM', 'error':'No client_secret'} valSessionStore = ThreePidValSessionStore(self.sydent) noMatchError = {'errcode': 'M_NO_VALID_SESSION', 'error': "No valid session was found matching that sid and client secret"} try: s = valSessionStore.getValidatedSession(sid, clientSecret) except IncorrectClientSecretException: return noMatchError except SessionExpiredException: return {'errcode': 'M_SESSION_EXPIRED', 'error': "This validation session has expired: call requestToken again"} except InvalidSessionIdException: return noMatchError except SessionNotValidatedException: return {'errcode': 'M_SESSION_NOT_VALIDATED', 'error': "This validation session has not yet been completed"} return { 'medium': s.medium, 'address': s.address, 'validated_at': s.mtime }
def addBinding(self, valSessionId, clientSecret, mxid): valSessionStore = ThreePidValSessionStore(self.sydent) localAssocStore = LocalAssociationStore(self.sydent) s = valSessionStore.getValidatedSession(valSessionId, clientSecret) createdAt = time_msec() expires = createdAt + ThreepidBinder.THREEPID_ASSOCIATION_LIFETIME_MS assoc = ThreepidAssociation(s.medium, s.address, mxid, createdAt, createdAt, expires) localAssocStore.addOrUpdateAssociation(assoc) self.sydent.pusher.doLocalPush() joinTokenStore = JoinTokenStore(self.sydent) pendingJoinTokens = joinTokenStore.getTokens(s.medium, s.address) invites = [] for token in pendingJoinTokens: token["mxid"] = mxid token["signed"] = { "mxid": mxid, "token": token["token"], } token["signed"] = signedjson.sign.sign_json(token["signed"], self.sydent.server_name, self.sydent.keyring.ed25519) invites.append(token) if invites: assoc.extra_fields["invites"] = invites joinTokenStore.markTokensAsSent(s.medium, s.address) assocSigner = AssociationSigner(self.sydent) sgassoc = assocSigner.signedThreePidAssociation(assoc) self._notify(sgassoc, 0) return sgassoc
def requestToken(self, phoneNumber, clientSecret, sendAttempt, nextLink): if str(phoneNumber.country_code) in self.smsRules: action = self.smsRules[str(phoneNumber.country_code)] if action == 'reject': raise DestinationRejectedException() valSessionStore = ThreePidValSessionStore(self.sydent) msisdn = phonenumbers.format_number( phoneNumber, phonenumbers.PhoneNumberFormat.E164)[1:] valSession = valSessionStore.getOrCreateTokenSession( medium='msisdn', address=msisdn, clientSecret=clientSecret) valSessionStore.setMtime(valSession.id, time_msec()) if int(valSession.sendAttemptNumber) >= int(sendAttempt): logger.info( "Not texting code because current send attempt (%d) is not less than given send attempt (%s)", int(sendAttempt), int(valSession.sendAttemptNumber)) return valSession.id smsBodyTemplate = self.sydent.cfg.get('sms', 'bodyTemplate') originator = self.getOriginator(phoneNumber) logger.info( "Attempting to text code %s to %s (country %d) with originator %s", valSession.token, msisdn, phoneNumber.country_code, originator) smsBody = smsBodyTemplate.format(token=valSession.token) self.omSms.sendTextSMS(smsBody, msisdn, originator) valSessionStore.setSendAttemptNumber(valSession.id, sendAttempt) return valSession.id
def requestToken(self, phoneNumber, clientSecret, sendAttempt, nextLink): if str(phoneNumber.country_code) in self.smsRules: action = self.smsRules[str(phoneNumber.country_code)] if action == 'reject': raise DestinationRejectedException() valSessionStore = ThreePidValSessionStore(self.sydent) msisdn = phonenumbers.format_number( phoneNumber, phonenumbers.PhoneNumberFormat.E164 )[1:] valSession = valSessionStore.getOrCreateTokenSession( medium='msisdn', address=msisdn, clientSecret=clientSecret ) valSessionStore.setMtime(valSession.id, time_msec()) if int(valSession.sendAttemptNumber) >= int(sendAttempt): logger.info("Not texting code because current send attempt (%d) is not less than given send attempt (%s)", int(sendAttempt), int(valSession.sendAttemptNumber)) return valSession.id smsBodyTemplate = self.sydent.cfg.get('sms', 'bodyTemplate') originator = self.getOriginator(phoneNumber) logger.info( "Attempting to text code %s to %s (country %d) with originator %s", valSession.token, msisdn, phoneNumber.country_code, originator ) smsBody = smsBodyTemplate.format(token=valSession.token) self.omSms.sendTextSMS(smsBody, msisdn, originator) valSessionStore.setSendAttemptNumber(valSession.id, sendAttempt) return valSession.id
def requestToken(self, emailAddress, clientSecret, sendAttempt, nextLink, ipaddress=None, brand=None): """ Creates or retrieves a validation session and sends an email to the corresponding email address with a token to use to verify the association. :param emailAddress: The email address to send the email to. :type emailAddress: unicode :param clientSecret: The client secret to use. :type clientSecret: unicode :param sendAttempt: The current send attempt. :type sendAttempt: int :param nextLink: The link to redirect the user to once they have completed the validation. :type nextLink: unicode :param ipaddress: The requester's IP address. :type ipaddress: str or None :param brand: A hint at a brand from the request. :type brand: str or None :return: The ID of the session created (or of the existing one if any) :rtype: int """ valSessionStore = ThreePidValSessionStore(self.sydent) valSession = valSessionStore.getOrCreateTokenSession( medium=u'email', address=emailAddress, clientSecret=clientSecret) valSessionStore.setMtime(valSession.id, time_msec()) templateFile = self.sydent.get_branded_template( brand, "verification_template.eml", ('email', 'email.template'), ) if int(valSession.sendAttemptNumber) >= int(sendAttempt): logger.info( "Not mailing code because current send attempt (%d) is not less than given send attempt (%s)", int(sendAttempt), int(valSession.sendAttemptNumber)) return valSession.id ipstring = ipaddress if ipaddress else u"an unknown location" substitutions = { 'ipaddress': ipstring, 'link': self.makeValidateLink(valSession, clientSecret, nextLink), 'token': valSession.token, } logger.info( "Attempting to mail code %s (nextLink: %s) to %s", valSession.token, nextLink, emailAddress, ) sendEmail(self.sydent, templateFile, emailAddress, substitutions) valSessionStore.setSendAttemptNumber(valSession.id, sendAttempt) return valSession.id
def __init__(self, cfg, reactor=twisted.internet.reactor): self.reactor = reactor self.config_file = get_config_file_path() self.cfg = cfg logger.info("Starting Sydent server") self.pidfile = self.cfg.get('general', "pidfile.path") self.db = SqliteDatabase(self).db self.server_name = self.cfg.get('general', 'server.name') if self.server_name == '': self.server_name = os.uname()[1] logger.warn(( "You had not specified a server name. I have guessed that this server is called '%s' " + "and saved this in the config file. If this is incorrect, you should edit server.name in " + "the config file.") % (self.server_name, )) self.cfg.set('general', 'server.name', self.server_name) self.save_config() if self.cfg.has_option("general", "sentry_dsn"): # Only import and start sentry SDK if configured. import sentry_sdk sentry_sdk.init(dsn=self.cfg.get("general", "sentry_dsn"), ) with sentry_sdk.configure_scope() as scope: scope.set_tag("sydent_server_name", self.server_name) if self.cfg.has_option("general", "prometheus_port"): import prometheus_client prometheus_client.start_http_server( port=self.cfg.getint("general", "prometheus_port"), addr=self.cfg.get("general", "prometheus_addr"), ) self.enable_v1_associations = parse_cfg_bool( self.cfg.get("general", "enable_v1_associations")) self.delete_tokens_on_bind = parse_cfg_bool( self.cfg.get("general", "delete_tokens_on_bind")) # See if a pepper already exists in the database # Note: This MUST be run before we start serving requests, otherwise lookups for # 3PID hashes may come in before we've completed generating them hashing_metadata_store = HashingMetadataStore(self) lookup_pepper = hashing_metadata_store.get_lookup_pepper() if not lookup_pepper: # No pepper defined in the database, generate one lookup_pepper = generateAlphanumericTokenOfLength(5) # Store it in the database and rehash 3PIDs hashing_metadata_store.store_lookup_pepper( sha256_and_url_safe_base64, lookup_pepper) self.validators = Validators() self.validators.email = EmailValidator(self) self.validators.msisdn = MsisdnValidator(self) self.keyring = Keyring() self.keyring.ed25519 = SydentEd25519(self).signing_key self.keyring.ed25519.alg = 'ed25519' self.sig_verifier = Verifier(self) self.servlets = Servlets() self.servlets.v1 = V1Servlet(self) self.servlets.v2 = V2Servlet(self) self.servlets.emailRequestCode = EmailRequestCodeServlet(self) self.servlets.emailValidate = EmailValidateCodeServlet(self) self.servlets.msisdnRequestCode = MsisdnRequestCodeServlet(self) self.servlets.msisdnValidate = MsisdnValidateCodeServlet(self) self.servlets.lookup = LookupServlet(self) self.servlets.bulk_lookup = BulkLookupServlet(self) self.servlets.hash_details = HashDetailsServlet(self, lookup_pepper) self.servlets.lookup_v2 = LookupV2Servlet(self, lookup_pepper) self.servlets.pubkey_ed25519 = Ed25519Servlet(self) self.servlets.pubkeyIsValid = PubkeyIsValidServlet(self) self.servlets.ephemeralPubkeyIsValid = EphemeralPubkeyIsValidServlet( self) self.servlets.threepidBind = ThreePidBindServlet(self) self.servlets.threepidUnbind = ThreePidUnbindServlet(self) self.servlets.replicationPush = ReplicationPushServlet(self) self.servlets.getValidated3pid = GetValidated3pidServlet(self) self.servlets.storeInviteServlet = StoreInviteServlet(self) self.servlets.blindlySignStuffServlet = BlindlySignStuffServlet(self) self.servlets.termsServlet = TermsServlet(self) self.servlets.accountServlet = AccountServlet(self) self.servlets.registerServlet = RegisterServlet(self) self.servlets.logoutServlet = LogoutServlet(self) self.threepidBinder = ThreepidBinder(self) self.sslComponents = SslComponents(self) self.clientApiHttpServer = ClientApiHttpServer(self) self.replicationHttpsServer = ReplicationHttpsServer(self) self.replicationHttpsClient = ReplicationHttpsClient(self) self.pusher = Pusher(self) # A dedicated validation session store just to clean up old sessions every N minutes self.cleanupValSession = ThreePidValSessionStore(self) cb = task.LoopingCall(self.cleanupValSession.deleteOldSessions) cb.clock = self.reactor cb.start(10 * 60.0)
def _async_render_POST(self, request): try: try: body = json.load(request.content) except ValueError: request.setResponseCode(400) request.write(json.dumps({'errcode': 'M_BAD_JSON', 'error': 'Malformed JSON'})) request.finish() return missing = [k for k in ("threepid", "mxid") if k not in body] if len(missing) > 0: request.setResponseCode(400) msg = "Missing parameters: "+(",".join(missing)) request.write(json.dumps({'errcode': 'M_MISSING_PARAMS', 'error': msg})) request.finish() return threepid = body['threepid'] mxid = body['mxid'] if 'medium' not in threepid or 'address' not in threepid: request.setResponseCode(400) request.write(json.dumps({'errcode': 'M_MISSING_PARAMS', 'error': 'Threepid lacks medium / address'})) request.finish() return # We now check for authentication in two different ways, depending # on the contents of the request. If the user has supplied "sid" # (the Session ID returned by Sydent during the original binding) # and "client_secret" fields, they are trying to provie that they # were the original author of the bind. We then check that what # they supply matches and if it does, allow the unbind. # # However if these fields are not supplied, we instead check # whether the request originated from a homeserver, and if so the # same homeserver that originally created the bind. We do this by # checking the signature of the request. If it all matches up, we # allow the unbind. # # Only one method of authentication is required. if 'sid' in body and 'client_secret' in body: sid = body['sid'] client_secret = body['client_secret'] valSessionStore = ThreePidValSessionStore(self.sydent) noMatchError = {'errcode': 'M_NO_VALID_SESSION', 'error': "No valid session was found matching that sid and client secret"} try: s = valSessionStore.getValidatedSession(sid, client_secret) except IncorrectClientSecretException: request.setResponseCode(401) request.write(json.dumps(noMatchError)) request.finish() return except InvalidSessionIdException: request.setResponseCode(401) request.write(json.dumps(noMatchError)) request.finish() return except SessionNotValidatedException: request.setResponseCode(403) request.write(json.dumps({ 'errcode': 'M_SESSION_NOT_VALIDATED', 'error': "This validation session has not yet been completed" })) return if s.medium != threepid['medium'] or s.address != threepid['address']: request.setResponseCode(403) request.write(json.dumps({ 'errcode': 'M_FORBIDDEN', 'error': 'Provided session information does not match medium/address combo', })) request.finish() return else: try: origin_server_name = yield self.sydent.sig_verifier.authenticate_request(request, body) except SignatureVerifyException as ex: request.setResponseCode(401) request.write(json.dumps({'errcode': 'M_FORBIDDEN', 'error': ex.message})) request.finish() return except NoAuthenticationError as ex: request.setResponseCode(401) request.write(json.dumps({'errcode': 'M_FORBIDDEN', 'error': ex.message})) request.finish() return except: logger.exception("Exception whilst authenticating unbind request") request.setResponseCode(500) request.write(json.dumps({'errcode': 'M_UNKNOWN', 'error': 'Internal Server Error'})) request.finish() return if not mxid.endswith(':' + origin_server_name): request.setResponseCode(403) request.write(json.dumps({'errcode': 'M_FORBIDDEN', 'error': 'Origin server name does not match mxid'})) request.finish() return try: res = self.sydent.threepidBinder.removeBinding(threepid, mxid) except ValueError: # User could have provided correct 3PID/sid/client_secret # details but not the correct mxid, which would cause the # binding removal to fail request.setResponseCode(400) request.write(json.dumps({'errcode': 'M_UNKNOWN', 'error': "Association between provided mxid and 3pid not found"})) request.finish() return request.write(json.dumps({})) request.finish() except Exception as ex: logger.exception("Exception whilst handling unbind") request.setResponseCode(500) request.write(json.dumps({'errcode': 'M_UNKNOWN', 'error': ex.message})) request.finish()
async def _async_render_POST(self, request: Request) -> None: try: try: # TODO: we should really validate that this gives us a dict, and # not some other json value like str, list, int etc # json.loads doesn't allow bytes in Python 3.5 body: JsonDict = json_decoder.decode( request.content.read().decode("UTF-8")) except ValueError: request.setResponseCode(HTTPStatus.BAD_REQUEST) request.write( dict_to_json_bytes({ "errcode": "M_BAD_JSON", "error": "Malformed JSON" })) request.finish() return missing = [k for k in ("threepid", "mxid") if k not in body] if len(missing) > 0: request.setResponseCode(HTTPStatus.BAD_REQUEST) msg = "Missing parameters: " + (",".join(missing)) request.write( dict_to_json_bytes({ "errcode": "M_MISSING_PARAMS", "error": msg })) request.finish() return threepid = body["threepid"] mxid = body["mxid"] if "medium" not in threepid or "address" not in threepid: request.setResponseCode(HTTPStatus.BAD_REQUEST) request.write( dict_to_json_bytes({ "errcode": "M_MISSING_PARAMS", "error": "Threepid lacks medium / address", })) request.finish() return # We now check for authentication in two different ways, depending # on the contents of the request. If the user has supplied "sid" # (the Session ID returned by Sydent during the original binding) # and "client_secret" fields, they are trying to prove that they # were the original author of the bind. We then check that what # they supply matches and if it does, allow the unbind. # # However if these fields are not supplied, we instead check # whether the request originated from a homeserver, and if so the # same homeserver that originally created the bind. We do this by # checking the signature of the request. If it all matches up, we # allow the unbind. # # Only one method of authentication is required. if "sid" in body and "client_secret" in body: sid = body["sid"] client_secret = body["client_secret"] if not is_valid_client_secret(client_secret): request.setResponseCode(HTTPStatus.BAD_REQUEST) request.write( dict_to_json_bytes({ "errcode": "M_INVALID_PARAM", "error": "Invalid client_secret provided", })) request.finish() return valSessionStore = ThreePidValSessionStore(self.sydent) try: s = valSessionStore.getValidatedSession(sid, client_secret) except (IncorrectClientSecretException, InvalidSessionIdException): request.setResponseCode(HTTPStatus.UNAUTHORIZED) request.write( dict_to_json_bytes({ "errcode": "M_NO_VALID_SESSION", "error": "No valid session was found matching that sid and client secret", })) request.finish() return except SessionNotValidatedException: request.setResponseCode(HTTPStatus.FORBIDDEN) request.write( dict_to_json_bytes({ "errcode": "M_SESSION_NOT_VALIDATED", "error": "This validation session has not yet been completed", })) return if s.medium != threepid["medium"] or s.address != threepid[ "address"]: request.setResponseCode(HTTPStatus.FORBIDDEN) request.write( dict_to_json_bytes({ "errcode": "M_FORBIDDEN", "error": "Provided session information does not match medium/address combo", })) request.finish() return else: try: origin_server_name = ( await self.sydent.sig_verifier.authenticate_request( request, body)) except SignatureVerifyException as ex: request.setResponseCode(HTTPStatus.UNAUTHORIZED) request.write( dict_to_json_bytes({ "errcode": "M_FORBIDDEN", "error": str(ex) })) request.finish() return except NoAuthenticationError as ex: request.setResponseCode(HTTPStatus.UNAUTHORIZED) request.write( dict_to_json_bytes({ "errcode": "M_FORBIDDEN", "error": str(ex) })) request.finish() return except InvalidServerName as ex: request.setResponseCode(HTTPStatus.BAD_REQUEST) request.write( dict_to_json_bytes({ "errcode": "M_INVALID_PARAM", "error": str(ex) })) request.finish() return except (DNSLookupError, ConnectError, ResponseFailed) as e: msg = (f"Unable to contact the Matrix homeserver to " f"authenticate request ({type(e).__name__})") logger.warning(msg) request.setResponseCode(HTTPStatus.INTERNAL_SERVER_ERROR) request.write( dict_to_json_bytes({ "errcode": "M_UNKNOWN", "error": msg, })) request.finish() return except Exception: logger.exception( "Exception whilst authenticating unbind request") request.setResponseCode(HTTPStatus.INTERNAL_SERVER_ERROR) request.write( dict_to_json_bytes({ "errcode": "M_UNKNOWN", "error": "Internal Server Error" })) request.finish() return if not mxid.endswith(":" + origin_server_name): request.setResponseCode(HTTPStatus.FORBIDDEN) request.write( dict_to_json_bytes({ "errcode": "M_FORBIDDEN", "error": "Origin server name does not match mxid", })) request.finish() return self.sydent.threepidBinder.removeBinding(threepid, mxid) request.write(dict_to_json_bytes({})) request.finish() except Exception as ex: logger.exception("Exception whilst handling unbind") request.setResponseCode(HTTPStatus.INTERNAL_SERVER_ERROR) request.write( dict_to_json_bytes({ "errcode": "M_UNKNOWN", "error": str(ex) })) request.finish()
def _async_render_POST(self, request): try: try: body = json.load(request.content) except ValueError: request.setResponseCode(400) request.write( json.dumps({ 'errcode': 'M_BAD_JSON', 'error': 'Malformed JSON' })) request.finish() return missing = [k for k in ("threepid", "mxid") if k not in body] if len(missing) > 0: request.setResponseCode(400) msg = "Missing parameters: " + (",".join(missing)) request.write( json.dumps({ 'errcode': 'M_MISSING_PARAMS', 'error': msg })) request.finish() return threepid = body['threepid'] mxid = body['mxid'] if 'medium' not in threepid or 'address' not in threepid: request.setResponseCode(400) request.write( json.dumps({ 'errcode': 'M_MISSING_PARAMS', 'error': 'Threepid lacks medium / address' })) request.finish() return # We now check for authentication in two different ways, depending # on the contents of the request. If the user has supplied "sid" # (the Session ID returned by Sydent during the original binding) # and "client_secret" fields, they are trying to provie that they # were the original author of the bind. We then check that what # they supply matches and if it does, allow the unbind. # # However if these fields are not supplied, we instead check # whether the request originated from a homeserver, and if so the # same homeserver that originally created the bind. We do this by # checking the signature of the request. If it all matches up, we # allow the unbind. # # Only one method of authentication is required. if 'sid' in body and 'client_secret' in body: sid = body['sid'] client_secret = body['client_secret'] valSessionStore = ThreePidValSessionStore(self.sydent) noMatchError = { 'errcode': 'M_NO_VALID_SESSION', 'error': "No valid session was found matching that sid and client secret" } try: s = valSessionStore.getValidatedSession(sid, client_secret) except IncorrectClientSecretException: request.setResponseCode(401) request.write(json.dumps(noMatchError)) request.finish() return except InvalidSessionIdException: request.setResponseCode(401) request.write(json.dumps(noMatchError)) request.finish() return except SessionNotValidatedException: request.setResponseCode(403) request.write( json.dumps({ 'errcode': 'M_SESSION_NOT_VALIDATED', 'error': "This validation session has not yet been completed" })) return if s.medium != threepid['medium'] or s.address != threepid[ 'address']: request.setResponseCode(403) request.write( json.dumps({ 'errcode': 'M_FORBIDDEN', 'error': 'Provided session information does not match medium/address combo', })) request.finish() return else: try: origin_server_name = yield self.sydent.sig_verifier.authenticate_request( request, body) except SignatureVerifyException as ex: request.setResponseCode(401) request.write( json.dumps({ 'errcode': 'M_FORBIDDEN', 'error': ex.message })) request.finish() return except NoAuthenticationError as ex: request.setResponseCode(401) request.write( json.dumps({ 'errcode': 'M_FORBIDDEN', 'error': ex.message })) request.finish() return except: logger.exception( "Exception whilst authenticating unbind request") request.setResponseCode(500) request.write( json.dumps({ 'errcode': 'M_UNKNOWN', 'error': 'Internal Server Error' })) request.finish() return if not mxid.endswith(':' + origin_server_name): request.setResponseCode(403) request.write( json.dumps({ 'errcode': 'M_FORBIDDEN', 'error': 'Origin server name does not match mxid' })) request.finish() return try: res = self.sydent.threepidBinder.removeBinding(threepid, mxid) except ValueError: # User could have provided correct 3PID/sid/client_secret # details but not the correct mxid, which would cause the # binding removal to fail request.setResponseCode(400) request.write( json.dumps({ 'errcode': 'M_UNKNOWN', 'error': "Association between provided mxid and 3pid not found" })) request.finish() return request.write(json.dumps({})) request.finish() except Exception as ex: logger.exception("Exception whilst handling unbind") request.setResponseCode(500) request.write( json.dumps({ 'errcode': 'M_UNKNOWN', 'error': ex.message })) request.finish()