Esempio n. 1
0
    def do_validate_request(self, request):
        """
        Extracts information about a validation session from the request and
        attempts to validate that session.

        :param request: The request to extract information about the session from.
        :type request: twisted.web.server.Request

        :return: A dict with a "success" key which value indicates whether the
            validation succeeded. If the validation failed, this dict also includes
            a "errcode" and a "error" keys which include information about the failure.
        :rtype: dict[str, bool or str]
        """
        args = get_args(request, ('token', 'sid', 'client_secret'))

        sid = args['sid']
        tokenString = args['token']
        clientSecret = args['client_secret']

        if not is_valid_client_secret(clientSecret):
            request.setResponseCode(400)
            return {
                'errcode': 'M_INVALID_PARAM',
                'error': 'Invalid client_secret provided'
            }

        try:
            return self.sydent.validators.email.validateSessionWithToken(
                sid, clientSecret, tokenString)
        except IncorrectClientSecretException:
            return {
                'success':
                False,
                'errcode':
                'M_INVALID_PARAM',
                'error':
                "Client secret does not match the one given when requesting the token"
            }
        except SessionExpiredException:
            return {
                'success':
                False,
                'errcode':
                'M_SESSION_EXPIRED',
                'error':
                "This validation session has expired: call requestToken again"
            }
        except InvalidSessionIdException:
            return {
                'success': False,
                'errcode': 'M_INVALID_PARAM',
                'error': "The token doesn't match"
            }
        except IncorrectSessionTokenException:
            return {
                'success': False,
                'errcode': 'M_NO_VALID_SESSION',
                'error': "No session could be found with this sid"
            }
Esempio n. 2
0
    def do_validate_request(self, request: Request) -> JsonDict:
        """
        Extracts information about a validation session from the request and
        attempts to validate that session.

        :param request: The request to extract information about the session from.

        :return: A dict with a "success" key which value indicates whether the
            validation succeeded. If the validation failed, this dict also includes
            a "errcode" and a "error" keys which include information about the failure.
        """

        args = get_args(request, ("token", "sid", "client_secret"))

        sid = args["sid"]
        tokenString = args["token"]
        clientSecret = args["client_secret"]

        if not is_valid_client_secret(clientSecret):
            request.setResponseCode(400)
            return {
                "errcode": "M_INVALID_PARAM",
                "error": "Invalid client_secret provided",
            }

        try:
            return self.sydent.validators.msisdn.validateSessionWithToken(
                sid, clientSecret, tokenString
            )
        except IncorrectClientSecretException:
            request.setResponseCode(400)
            return {
                "success": False,
                "errcode": "M_INVALID_PARAM",
                "error": "Client secret does not match the one given when requesting the token",
            }
        except SessionExpiredException:
            request.setResponseCode(400)
            return {
                "success": False,
                "errcode": "M_SESSION_EXPIRED",
                "error": "This validation session has expired: call requestToken again",
            }
        except InvalidSessionIdException:
            request.setResponseCode(400)
            return {
                "success": False,
                "errcode": "M_INVALID_PARAM",
                "error": "The token doesn't match",
            }
        except IncorrectSessionTokenException:
            request.setResponseCode(404)
            return {
                "success": False,
                "errcode": "M_NO_VALID_SESSION",
                "error": "No session could be found with this sid",
            }
Esempio n. 3
0
    def render_POST(self, request):
        send_cors(request)

        account = authIfV2(self.sydent, request)

        args = get_args(request, ('sid', 'client_secret', 'mxid'))

        sid = args['sid']
        mxid = args['mxid']
        clientSecret = args['client_secret']

        if not is_valid_client_secret(clientSecret):
            request.setResponseCode(400)
            return {
                'errcode': 'M_INVALID_PARAM',
                'error': 'Invalid client_secret provided'
            }

        # 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"
        }

        if account:
            # This is a v2 API so only allow binding to the logged in user id
            if account.userId != mxid:
                raise MatrixRestError(
                    403, 'M_UNAUTHORIZED',
                    "This user is prohibited from binding to the mxid")

        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
Esempio n. 4
0
    def render_POST(self, request):
        send_cors(request)

        authIfV2(self.sydent, request)

        args = get_args(request, ('phone_number', 'country', 'client_secret', 'send_attempt'))

        raw_phone_number = args['phone_number']
        country = args['country']
        sendAttempt = args['send_attempt']
        clientSecret = args['client_secret']

        if not is_valid_client_secret(clientSecret):
            request.setResponseCode(400)
            return {
                'errcode': 'M_INVALID_PARAM',
                'error': 'Invalid client_secret provided'
            }

        try:
            phone_number_object = phonenumbers.parse(raw_phone_number, country)
        except Exception as e:
            logger.warn("Invalid phone number given: %r", e)
            request.setResponseCode(400)
            return {'errcode': 'M_INVALID_PHONE_NUMBER', 'error': "Invalid phone number" }

        msisdn = phonenumbers.format_number(
            phone_number_object, phonenumbers.PhoneNumberFormat.E164
        )[1:]

        # International formatted number. The same as an E164 but with spaces
        # in appropriate places to make it nicer for the humans.
        intl_fmt = phonenumbers.format_number(
            phone_number_object, phonenumbers.PhoneNumberFormat.INTERNATIONAL
        )

        try:
            sid = self.sydent.validators.msisdn.requestToken(
                phone_number_object, clientSecret, sendAttempt
            )
            resp = {
                'success': True, 'sid': str(sid),
                'msisdn': msisdn, 'intl_fmt': intl_fmt,
            }
        except DestinationRejectedException:
            logger.error("Destination rejected for number: %s", msisdn)
            request.setResponseCode(400)
            resp = {'errcode': 'M_DESTINATION_REJECTED', 'error': 'Phone numbers in this country are not currently supported'}
        except Exception as e:
            logger.error("Exception sending SMS: %r", e)
            request.setResponseCode(500)
            resp = {'errcode': 'M_UNKNOWN', 'error': 'Internal Server Error'}

        return resp
Esempio n. 5
0
    def render_POST(self, request: Request) -> JsonDict:
        send_cors(request)

        account = None
        if self.require_auth:
            account = authV2(self.sydent, request)

        args = get_args(request, ("sid", "client_secret", "mxid"))

        sid = args["sid"]
        mxid = args["mxid"]
        clientSecret = args["client_secret"]

        if not is_valid_client_secret(clientSecret):
            raise MatrixRestError(400, "M_INVALID_PARAM",
                                  "Invalid client_secret provided")

        if account:
            # This is a v2 API so only allow binding to the logged in user id
            if account.userId != mxid:
                raise MatrixRestError(
                    403,
                    "M_UNAUTHORIZED",
                    "This user is prohibited from binding to the mxid",
                )

        try:
            valSessionStore = ThreePidValSessionStore(self.sydent)
            s = valSessionStore.getValidatedSession(sid, clientSecret)
        except (IncorrectClientSecretException, InvalidSessionIdException):
            # Return the same error for not found / bad client secret otherwise
            # people can get information about sessions without knowing the
            # secret.
            raise MatrixRestError(
                404,
                "M_NO_VALID_SESSION",
                "No valid session was found matching that sid and client secret",
            )
        except SessionExpiredException:
            raise MatrixRestError(
                400,
                "M_SESSION_EXPIRED",
                "This validation session has expired: call requestToken again",
            )
        except SessionNotValidatedException:
            raise MatrixRestError(
                400,
                "M_SESSION_NOT_VALIDATED",
                "This validation session has not yet been completed",
            )

        res = self.sydent.threepidBinder.addBinding(s.medium, s.address, mxid)
        return res
    def render_GET(self, request: Request) -> JsonDict:
        send_cors(request)
        if self.require_auth:
            authV2(self.sydent, request)

        args = get_args(request, ("sid", "client_secret"))

        sid = args["sid"]
        clientSecret = args["client_secret"]

        if not is_valid_client_secret(clientSecret):
            request.setResponseCode(400)
            return {
                "errcode": "M_INVALID_PARAM",
                "error": "Invalid client_secret provided",
            }

        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, InvalidSessionIdException):
            request.setResponseCode(404)
            return noMatchError
        except SessionExpiredException:
            request.setResponseCode(400)
            return {
                "errcode":
                "M_SESSION_EXPIRED",
                "error":
                "This validation session has expired: call requestToken again",
            }
        except SessionNotValidatedException:
            request.setResponseCode(400)
            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
        }
Esempio n. 7
0
    def render_POST(self, request):
        send_cors(request)

        authIfV2(self.sydent, request)

        args = get_args(request, ('email', 'client_secret', 'send_attempt'))

        email = args['email']
        sendAttempt = args['send_attempt']
        clientSecret = args['client_secret']

        if not is_valid_client_secret(clientSecret):
            request.setResponseCode(400)
            return {
                'errcode': 'M_INVALID_PARAM',
                'error': 'Invalid client_secret provided'
            }

        ipaddress = self.sydent.ip_from_request(request)
        brand = self.sydent.brand_from_request(request)

        nextLink = None
        if 'next_link' in args and not args['next_link'].startswith(
                "file:///"):
            nextLink = args['next_link']

        try:
            sid = self.sydent.validators.email.requestToken(
                email,
                clientSecret,
                sendAttempt,
                nextLink,
                ipaddress=ipaddress,
                brand=brand,
            )
            resp = {'sid': str(sid)}
        except EmailAddressException:
            request.setResponseCode(400)
            resp = {
                'errcode': 'M_INVALID_EMAIL',
                'error': 'Invalid email address'
            }
        except EmailSendException:
            request.setResponseCode(500)
            resp = {
                'errcode': 'M_EMAIL_SEND_ERROR',
                'error': 'Failed to send email'
            }

        return resp
Esempio n. 8
0
    def render_GET(self, request):
        authIfV2(self.sydent, request)

        args = get_args(request, ('sid', 'client_secret'))

        sid = args['sid']
        clientSecret = args['client_secret']

        if not is_valid_client_secret(clientSecret):
            request.setResponseCode(400)
            return {
                'errcode': 'M_INVALID_PARAM',
                'error': 'Invalid client_secret provided'
            }

        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, InvalidSessionIdException):
            request.setResponseCode(404)
            return noMatchError
        except SessionExpiredException:
            request.setResponseCode(400)
            return {
                'errcode':
                'M_SESSION_EXPIRED',
                'error':
                "This validation session has expired: call requestToken again"
            }
        except SessionNotValidatedException:
            request.setResponseCode(400)
            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
        }
Esempio n. 9
0
    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()
Esempio n. 10
0
    async def render_POST(self, request: Request) -> JsonDict:
        send_cors(request)

        if self.require_auth:
            authV2(self.sydent, request)

        args = get_args(
            request, ("phone_number", "country", "client_secret", "send_attempt")
        )

        raw_phone_number = args["phone_number"]
        country = args["country"]
        try:
            # See the comment handling `send_attempt` in emailservlet.py for
            # more context.
            sendAttempt = int(args["send_attempt"])
        except (TypeError, ValueError):
            request.setResponseCode(400)
            return {
                "errcode": "M_INVALID_PARAM",
                "error": f"send_attempt should be an integer (got {args['send_attempt']}",
            }
        clientSecret = args["client_secret"]

        if not is_valid_client_secret(clientSecret):
            request.setResponseCode(400)
            return {
                "errcode": "M_INVALID_PARAM",
                "error": "Invalid client_secret provided",
            }

        try:
            phone_number_object = phonenumbers.parse(raw_phone_number, country)
        except Exception as e:
            logger.warning("Invalid phone number given: %r", e)
            request.setResponseCode(400)
            return {
                "errcode": "M_INVALID_PHONE_NUMBER",
                "error": "Invalid phone number",
            }

        msisdn = phonenumbers.format_number(
            phone_number_object, phonenumbers.PhoneNumberFormat.E164
        )[1:]

        # International formatted number. The same as an E164 but with spaces
        # in appropriate places to make it nicer for the humans.
        intl_fmt = phonenumbers.format_number(
            phone_number_object, phonenumbers.PhoneNumberFormat.INTERNATIONAL
        )

        brand = self.sydent.brand_from_request(request)
        try:
            sid = await self.sydent.validators.msisdn.requestToken(
                phone_number_object, clientSecret, sendAttempt, brand
            )
            resp = {
                "success": True,
                "sid": str(sid),
                "msisdn": msisdn,
                "intl_fmt": intl_fmt,
            }
        except DestinationRejectedException:
            logger.warning("Destination rejected for number: %s", msisdn)
            request.setResponseCode(400)
            resp = {
                "errcode": "M_DESTINATION_REJECTED",
                "error": "Phone numbers in this country are not currently supported",
            }
        except Exception:
            logger.exception("Exception sending SMS")
            request.setResponseCode(500)
            resp = {"errcode": "M_UNKNOWN", "error": "Internal Server Error"}

        return resp
Esempio n. 11
0
    def render_POST(self, request: Request) -> JsonDict:
        send_cors(request)

        if self.require_auth:
            authV2(self.sydent, request)

        args = get_args(request, ("email", "client_secret", "send_attempt"))

        email = args["email"]
        clientSecret = args["client_secret"]

        try:
            # if we got this via the v1 API in a querystring or urlencoded body,
            # then the values in args will be a string. So check that
            # send_attempt is an int.
            #
            # NB: We don't check if we're processing a url-encoded v1 request.
            # This means we accept string representations of integers for
            # `send_attempt` in v2 requests, and in v1 requests that supply a
            # JSON body. This is contrary to the spec and leaves me with a dirty
            # feeling I can't quite shake off.
            #
            # Where's Raymond Hettinger when you need him? (THUMP) There must be
            # a better way!
            sendAttempt = int(args["send_attempt"])
        except (TypeError, ValueError):
            request.setResponseCode(400)
            return {
                "errcode":
                "M_INVALID_PARAM",
                "error":
                f"send_attempt should be an integer (got {args['send_attempt']}",
            }

        if not is_valid_client_secret(clientSecret):
            request.setResponseCode(400)
            return {
                "errcode": "M_INVALID_PARAM",
                "error": "Invalid client_secret provided",
            }

        if not (0 < len(email) <= MAX_EMAIL_ADDRESS_LENGTH):
            request.setResponseCode(400)
            return {
                "errcode": "M_INVALID_PARAM",
                "error": "Invalid email provided"
            }

        ipaddress = self.sydent.ip_from_request(request)
        brand = self.sydent.brand_from_request(request)

        nextLink: Optional[str] = None
        if "next_link" in args and not args["next_link"].startswith(
                "file:///"):
            nextLink = args["next_link"]

        try:
            sid = self.sydent.validators.email.requestToken(
                email,
                clientSecret,
                sendAttempt,
                nextLink,
                ipaddress=ipaddress,
                brand=brand,
            )
            resp = {"sid": str(sid)}
        except EmailAddressException:
            request.setResponseCode(400)
            resp = {
                "errcode": "M_INVALID_EMAIL",
                "error": "Invalid email address"
            }
        except EmailSendException:
            request.setResponseCode(500)
            resp = {
                "errcode": "M_EMAIL_SEND_ERROR",
                "error": "Failed to send email"
            }

        return resp
Esempio n. 12
0
    def _async_render_POST(self, request):
        try:
            try:
                # json.loads doesn't allow bytes in Python 3.5
                body = json.loads(request.content.read().decode("UTF-8"))
            except ValueError:
                request.setResponseCode(400)
                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(400)
                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(400)
                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(400)
                    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(401)
                    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(403)
                    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(403)
                    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 = yield self.sydent.sig_verifier.authenticate_request(
                        request, body)
                except SignatureVerifyException as ex:
                    request.setResponseCode(401)
                    request.write(
                        dict_to_json_bytes({
                            'errcode': 'M_FORBIDDEN',
                            'error': str(ex)
                        }))
                    request.finish()
                    return
                except NoAuthenticationError as ex:
                    request.setResponseCode(401)
                    request.write(
                        dict_to_json_bytes({
                            'errcode': 'M_FORBIDDEN',
                            'error': str(ex)
                        }))
                    request.finish()
                    return
                except:
                    logger.exception(
                        "Exception whilst authenticating unbind request")
                    request.setResponseCode(500)
                    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(403)
                    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(500)
            request.write(
                dict_to_json_bytes({
                    'errcode': 'M_UNKNOWN',
                    'error': str(ex)
                }))
            request.finish()