예제 #1
0
    def render_POST(self, request):
        send_cors(request)

        error, args = get_args(request, ('email', 'client_secret', 'send_attempt'))
        if error:
            request.setResponseCode(400)
            return error

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

        ipaddress = self.sydent.ip_from_request(request)

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

        resp = None

        try:
            sid = self.sydent.validators.email.requestToken(
                email, clientSecret, sendAttempt, nextLink, ipaddress=ipaddress
            )
        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'}

        if not resp:
            resp = {'success': True, 'sid': str(sid)}

        return resp
예제 #2
0
    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
예제 #3
0
    def render_GET(self, request: Request) -> JsonDict:
        args = get_args(request, ("public_key", ))
        publicKey = args["public_key"]

        return {
            "valid": self.joinTokenStore.validateEphemeralPublicKey(publicKey),
        }
예제 #4
0
    def render_POST(self, request):
        """
        Bulk-lookup for threepids.
        Params: 'threepids': list of threepids, each of which is a list of medium, address
        Returns: Object with key 'threepids', which is a list of results where each result
                 is a 3 item list of medium, address, mxid
                 Note that results are not streamed to the client.
        Threepids for which no mapping is found are omitted.
        """
        send_cors(request)

        authIfV2(self.sydent, request)

        args = get_args(request, ('threepids', ))

        threepids = args['threepids']
        if not isinstance(threepids, list):
            raise MatrixRestError(400, 'M_INVALID_PARAM',
                                  'threepids must be a list')

        logger.info("Bulk lookup of %d threepids", len(threepids))

        globalAssocStore = GlobalAssociationStore(self.sydent)
        results = globalAssocStore.getMxids(threepids)

        return {'threepids': results}
예제 #5
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']
        clientSecret = args['client_secret']
        sendAttempt = args['send_attempt']

        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)

        resp = None

        try:
            sid = self.sydent.validators.msisdn.requestToken(
                phone_number_object, clientSecret, sendAttempt, None)
        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'}

        if not resp:
            resp = {
                'success': True,
                'sid': str(sid),
                'msisdn': msisdn,
                'intl_fmt': intl_fmt,
            }

        return resp
예제 #6
0
    def render_GET(self, request):
        args = get_args(request, ("public_key",))

        pubKey = self.sydent.keyring.ed25519.verify_key
        pubKeyBase64 = encode_base64(pubKey.encode())

        return {'valid': args["public_key"] == pubKeyBase64}
예제 #7
0
    def render_POST(self, request):
        """
        Mark a set of terms and conditions as having been agreed to
        """
        send_cors(request)

        account = authIfV2(self.sydent, request, False)

        args = get_args(request, ("user_accepts",))

        user_accepts = args["user_accepts"]

        terms = get_terms(self.sydent)
        unknown_urls = list(set(user_accepts) - terms.getUrlSet())
        if len(unknown_urls) > 0:
            return {
                "errcode": "M_UNKNOWN",
                "error": "Unrecognised URLs: %s" % (', '.join(unknown_urls),),
            }

        termsStore = TermsStore(self.sydent)
        termsStore.addAgreedUrls(account.userId, user_accepts)

        all_accepted_urls = termsStore.getAgreedUrls(account.userId)

        if terms.urlListIsSufficient(all_accepted_urls):
            accountStore = AccountStore(self.sydent)
            accountStore.setConsentVersion(account.userId, terms.getMasterVersion())

        return {}
예제 #8
0
    def render_POST(self, request):
        """
        Bulk-lookup for threepids.
        ** DEPRECATED **
        Use /bulk_lookup which returns the result encapsulated in a dict
        Params: 'threepids': list of threepids, each of which is a list of medium, address
        Returns: List of results where each result is a 3 item list of medium, address, mxid
        Threepids for which no mapping is found are omitted.
        """
        send_cors(request)

        authIfV2(self.sydent, request)

        args = get_args(request, ('threepids',))

        threepids = args['threepids']
        if not isinstance(threepids, list):
            request.setResponseCode(400)
            return {'errcode': 'M_INVALID_PARAM', 'error': 'threepids must be a list'}, None

        logger.info("Bulk lookup of %d threepids (deprecated endpoint)", len(threepids))
            
        globalAssocStore = GlobalAssociationStore(self.sydent)
        results = globalAssocStore.getMxids(threepids)

        return json.dumps(results)
예제 #9
0
    def render_POST(self, request):
        """
        Bulk-lookup for threepids.
        ** DEPRECATED **
        Use /bulk_lookup which returns the result encapsulated in a dict
        Params: 'threepids': list of threepids, each of which is a list of medium, address
        Returns: List of results where each result is a 3 item list of medium, address, mxid
        Threepids for which no mapping is found are omitted.
        """
        send_cors(request)
        err, args = get_args(request, ('threepids',))
        if err:
            return json.dumps(err)

        threepids = args['threepids']
        if not isinstance(threepids, list):
            request.setResponseCode(400)
            return {'errcode': 'M_INVALID_PARAM', 'error': 'threepids must be a list'}, None

        logger.info("Bulk lookup of %d threepids (deprecated endpoint)", len(threepids))
            
        globalAssocStore = GlobalAssociationStore(self.sydent)
        results = globalAssocStore.getMxids(threepids)

        return json.dumps(results)
예제 #10
0
    def render_GET(self, request: Request) -> str:
        send_cors(request)

        args = get_args(request, ("token", "sid", "client_secret"))
        resp = self.do_validate_request(request)
        if "success" in resp and resp["success"]:
            msg = "Verification successful! Please return to your Matrix client to continue."
            if "next_link" in args:
                next_link = args["next_link"]
                request.setResponseCode(302)
                request.setHeader("Location", next_link)
        else:
            request.setResponseCode(400)
            msg = (
                "Verification failed: you may need to request another verification text"
            )

        brand = self.sydent.brand_from_request(request)

        # self.sydent.config.http.verify_response_template is deprecated
        if self.sydent.config.http.verify_response_template is None:
            templateFile = self.sydent.get_branded_template(
                brand,
                "verify_response_template.html",
            )
        else:
            templateFile = self.sydent.config.http.verify_response_template

        request.setHeader("Content-Type", "text/html")
        return open(templateFile).read() % {"message": msg}
예제 #11
0
    def render_POST(self, request):
        """
        Bulk-lookup for threepids.
        Params: 'threepids': list of threepids, each of which is a list of medium, address
        Returns: Object with key 'threepids', which is a list of results where each result
                 is a 3 item list of medium, address, mxid
                 Note that results are not streamed to the client.
        Threepids for which no mapping is found are omitted.
        """
        send_cors(request)
        err, args = get_args(request, ('threepids',))
        if err:
            return json.dumps(err)

        threepids = args['threepids']
        if not isinstance(threepids, list):
            request.setResponseCode(400)
            return {'errcode': 'M_INVALID_PARAM', 'error': 'threepids must be a list'}, None

        logger.info("Bulk lookup of %d threepids", len(threepids))

        globalAssocStore = GlobalAssociationStore(self.sydent)
        results = globalAssocStore.getMxids(threepids)

        return json.dumps({ 'threepids': results })
예제 #12
0
    def render_GET(self, request):
        send_cors(request)

        err, args = get_args(request, ('token', 'sid', 'client_secret'))
        if err:
            msg = "Verification failed: Your request was invalid."
        else:
            resp = self.do_validate_request(args)
            if 'success' in resp and resp['success']:
                msg = "Verification successful! Please return to your Matrix client to continue."
                if 'next_link' in args:
                    next_link = args['next_link']
                    request.setResponseCode(302)
                    request.setHeader("Location", next_link)
            else:
                request.setResponseCode(400)
                msg = "Verification failed: you may need to request another verification text"

        brand = self.sydent.brand_from_request(request)
        templateFile = self.sydent.get_branded_template(
            brand,
            "verify_response_template.html",
            ('http', 'verify_response_template'),
        )

        request.setHeader("Content-Type", "text/html")
        return open(templateFile).read() % {'message': msg}
예제 #13
0
    def render_POST(self, request):
        send_cors(request)
        err, args = get_args(request, ("private_key", "token", "mxid"))
        if err:
            return json.dumps(err)

        private_key_base64 = args['private_key']
        token = args['token']
        mxid = args['mxid']

        sender = self.tokenStore.getSenderForToken(token)
        if sender is None:
            request.setResponseCode(404)
            return json.dumps({
                "errcode": "M_UNRECOGNIZED",
                "error": "Didn't recognize token",
            })

        to_sign = {
            "mxid": mxid,
            "sender": sender,
            "token": token,
        }
        try:
            private_key = signedjson.key.decode_signing_key_base64(
                "ed25519", "0", private_key_base64)
            signed = signedjson.sign.sign_json(to_sign, self.server_name,
                                               private_key)
        except:
            return json.dumps({
                "errcode": "M_UNKNOWN",
            })

        return json.dumps(signed)
예제 #14
0
    def render_GET(self, request):
        args = get_args(request, ('nextLink', ), required=False)

        resp = None
        try:
            resp = self.do_validate_request(request)
        except:
            pass
        if resp and 'success' in resp and resp['success']:
            msg = "Verification successful! Please return to your Matrix client to continue."
            if 'nextLink' in args:
                next_link = args['nextLink']
                if not next_link.startswith("file:///"):
                    request.setResponseCode(302)
                    request.setHeader("Location", next_link)
        else:
            msg = "Verification failed: you may need to request another verification email"

        brand = self.sydent.brand_from_request(request)
        templateFile = self.sydent.get_branded_template(
            brand,
            "verify_response_template.html",
            ('http', 'verify_response_template'),
        )

        request.setHeader("Content-Type", "text/html")
        res = open(templateFile).read() % {'message': msg}
        return res.encode("UTF-8")
    def do_validate_request(self, request):
        send_cors(request)

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

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

        try:
            resp = self.sydent.validators.email.validateSessionWithToken(
                sid, clientSecret, tokenString)
        except IncorrectClientSecretException:
            return {
                'success':
                False,
                'errcode':
                'M_INCORRECT_CLIENT_SECRET',
                '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"
            }

        if not resp:
            resp = {'success': False}

        return resp
예제 #16
0
    def render_POST(self, request: Request) -> JsonDict:
        send_cors(request)

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

        args = get_args(request, ("private_key", "token", "mxid"))

        private_key_base64 = args["private_key"]
        token = args["token"]
        mxid = args["mxid"]

        sender = self.tokenStore.getSenderForToken(token)
        if sender is None:
            raise MatrixRestError(404, "M_UNRECOGNIZED", "Didn't recognize token")

        to_sign = {
            "mxid": mxid,
            "sender": sender,
            "token": token,
        }
        try:
            private_key = signedjson.key.decode_signing_key_base64(
                "ed25519", "0", private_key_base64
            )
            signed: JsonDict = signedjson.sign.sign_json(
                to_sign, self.server_name, private_key
            )
        except Exception:
            logger.exception("signing failed")
            raise MatrixRestError(500, "M_UNKNOWN", "Internal Server Error")

        return signed
예제 #17
0
    def render_POST(self, request):
        """
        Register with the Identity Server
        """
        send_cors(request)

        args = get_args(request, ('matrix_server_name', 'access_token'))

        result = yield self.client.get_json(
            "matrix://%s/_matrix/federation/v1/openid/userinfo?access_token=%s"
            % (
                args['matrix_server_name'],
                urllib.parse.quote(args['access_token']),
            ), )
        if 'sub' not in result:
            raise Exception("Invalid response from homeserver")

        user_id = result['sub']
        tok = yield issueToken(self.sydent, user_id)

        # XXX: `token` is correct for the spec, but we released with `access_token`
        # for a substantial amount of time. Serve both to make spec-compliant clients
        # happy.
        defer.returnValue({
            "access_token": tok,
            "token": tok,
        })
예제 #18
0
    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 }
예제 #19
0
    def render_GET(self, request: Request) -> bytes:
        args = get_args(request, ("nextLink", ), required=False)

        resp = None
        try:
            resp = self.do_validate_request(request)
        except Exception:
            pass
        if resp and "success" in resp and resp["success"]:
            msg = "Verification successful! Please return to your Matrix client to continue."
            if "nextLink" in args:
                next_link = args["nextLink"]
                if not next_link.startswith("file:///"):
                    request.setResponseCode(302)
                    request.setHeader("Location", next_link)
        else:
            msg = "Verification failed: you may need to request another verification email"

        brand = self.sydent.brand_from_request(request)

        # self.sydent.config.http.verify_response_template is deprecated
        if self.sydent.config.http.verify_response_template is None:
            templateFile = self.sydent.get_branded_template(
                brand,
                "verify_response_template.html",
            )
        else:
            templateFile = self.sydent.config.http.verify_response_template

        request.setHeader("Content-Type", "text/html")
        res = open(templateFile).read() % {"message": msg}

        return res.encode("UTF-8")
예제 #20
0
def tokenFromRequest(request):
    """Extract token from header of query parameter.

    :param request: The request to look for an access token in.
    :type request: twisted.web.server.Request

    :return: The token or None if not found
    :rtype: unicode or None
    """
    token = None
    # check for Authorization header first
    authHeader = request.getHeader('Authorization')
    if authHeader is not None and authHeader.startswith('Bearer '):
        token = authHeader[len("Bearer "):]

    # no? try access_token query param
    if token is None:
        args = get_args(request, ('access_token', ), required=False)
        token = args.get('access_token')

    # Ensure we're dealing with unicode.
    if token and isinstance(token, bytes):
        token = token.decode("UTF-8")

    return token
예제 #21
0
    def render_GET(self, request):
        args = get_args(request, ("public_key",))
        publicKey = args["public_key"]

        return {
            'valid': self.joinTokenStore.validateEphemeralPublicKey(publicKey),
        }
예제 #22
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"
            }
예제 #23
0
    def render_POST(self, request):
        send_cors(request)

        err, args = get_args(request, ('token', 'sid', 'client_secret'))
        if err:
            return err

        return self.do_validate_request(args)
 def render_POST(self, request):
     send_cors(request)
     err, args = get_args(request, ('medium', 'address', 'mxid'))
     if err:
         return err
     return self.sydent.threepidBinder.addBinding(
         args['medium'], args['address'], args['mxid'],
     )
예제 #25
0
    def render_POST(self, request):
        send_cors(request)

        err, args = get_args(request, ('token', 'sid', 'client_secret'))
        if err:
            return err

        return self.do_validate_request(args)
예제 #26
0
    def render_POST(self, request):
        send_cors(request)

        authIfV2(self.sydent, request)

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

        return self.do_validate_request(args)
예제 #27
0
    def render_GET(self, request):
        err, args = get_args(request, ("public_key",))
        if err:
            return json.dumps(err)
        publicKey = args["public_key"]

        return json.dumps({
            'valid': self.joinTokenStore.validateEphemeralPublicKey(publicKey),
        })
예제 #28
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",
            }
예제 #29
0
    def render_GET(self, request):
        err, args = get_args(request, ("public_key",))
        if err:
            return json.dumps(err)

        pubKey = self.sydent.keyring.ed25519.verify_key
        pubKeyBase64 = encode_base64(pubKey.encode())

        return json.dumps({'valid': args["public_key"] == pubKeyBase64})
예제 #30
0
    def render_POST(self, request):
        send_cors(request)
        args = get_args(request, ('medium', 'address', 'mxid'))

        return self.sydent.threepidBinder.addBinding(
            args['medium'],
            args['address'],
            args['mxid'],
        )
    def render_POST(self, request: Request) -> JsonDict:
        send_cors(request)
        args = get_args(request, ("medium", "address", "mxid"))

        return self.sydent.threepidBinder.addBinding(
            args["medium"],
            args["address"],
            args["mxid"],
        )
예제 #32
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
예제 #33
0
    def render_POST(self, request):
        send_cors(request)
        args = get_args(request, ('medium', 'address', 'mxid'))

        threepid = {'medium': args['medium'], 'address': args['address']}

        return self.sydent.threepidBinder.removeBinding(
            threepid,
            args['mxid'],
        )
예제 #34
0
    def render_GET(self, request):
        err, args = get_args(request, ("public_key", ))
        if err:
            return json.dumps(err)
        publicKey = args["public_key"]

        return json.dumps({
            'valid':
            self.joinTokenStore.validateEphemeralPublicKey(publicKey),
        })
예제 #35
0
    def render_POST(self, request: Request) -> JsonDict:
        send_cors(request)
        args = get_args(request, ("medium", "address", "mxid"))

        threepid = {"medium": args["medium"], "address": args["address"]}

        self.sydent.threepidBinder.removeBinding(
            threepid,
            args["mxid"],
        )
        return {}
예제 #36
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
예제 #37
0
    def render_POST(self, request):
        send_cors(request)

        error, args = get_args(request, ('phone_number', 'country', 'client_secret', 'send_attempt'))
        if error:
            request.setResponseCode(400)
            return error

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

        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
        )

        resp = None

        try:
            sid = self.sydent.validators.msisdn.requestToken(
                phone_number_object, clientSecret, sendAttempt, None
            )
        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'}

        if not resp:
            resp = {
                'success': True, 'sid': str(sid),
                'msisdn': msisdn, 'intl_fmt': intl_fmt,
            }

        return resp
예제 #38
0
    def render_GET(self, request: Request) -> JsonDict:
        """
        Look up an individual threepid.

        ** DEPRECATED **

        Params: 'medium': the medium of the threepid
                'address': the address of the threepid
        Returns: A signed association if the threepid has a corresponding mxid, otherwise the empty object.
        """
        send_cors(request)

        args = get_args(request, ("medium", "address"))

        medium = args["medium"]
        address = args["address"]

        globalAssocStore = GlobalAssociationStore(self.sydent)

        sgassoc_raw = globalAssocStore.signedAssociationStringForThreepid(
            medium, address)

        if not sgassoc_raw:
            return {}

        # TODO validate this really is a dict
        sgassoc: JsonDict = json_decoder.decode(sgassoc_raw)
        if self.sydent.config.general.server_name not in sgassoc["signatures"]:
            # We have not yet worked out what the proper trust model should be.
            #
            # Maybe clients implicitly trust a server they talk to (and so we
            # should sign every assoc we return as ourselves, so they can
            # verify this).
            #
            # Maybe clients really want to know what server did the original
            # verification, and want to only know exactly who signed the assoc.
            #
            # Until we work out what we should do, sign all assocs we return as
            # ourself. This is vaguely ok because there actually is only one
            # identity server, but it happens to have two names (matrix.org and
            # vector.im), and so we're not really lying too much.
            #
            # We do this when we return assocs, not when we receive them over
            # replication, so that we can undo this decision in the future if
            # we wish, without having destroyed the raw underlying data.
            sgassoc = signedjson.sign.sign_json(
                sgassoc,
                self.sydent.config.general.server_name,
                self.sydent.keyring.ed25519,
            )
        return sgassoc
예제 #39
0
    def render_GET(self, request):
        """
        Look up an individual threepid.
        Params: 'medium': the medium of the threepid
                'address': the address of the threepid
        Returns: A signed association if the threepid has a corresponding mxid, otherwise the empty object.
        """
        send_cors(request)
        err, args = get_args(request, ('medium', 'address'))
        if err:
            return json.dumps(err)

        medium = args['medium']
        address = args['address']

        globalAssocStore = GlobalAssociationStore(self.sydent)

        sgassoc = globalAssocStore.signedAssociationStringForThreepid(medium, address)

        if not sgassoc:
            return json.dumps({})

        sgassoc = json.loads(sgassoc.encode('utf8'))
        if not self.sydent.server_name in sgassoc['signatures']:
            # We have not yet worked out what the proper trust model should be.
            #
            # Maybe clients implicitly trust a server they talk to (and so we
            # should sign every assoc we return as ourselves, so they can
            # verify this).
            #
            # Maybe clients really want to know what server did the original
            # verification, and want to only know exactly who signed the assoc.
            #
            # Until we work out what we should do, sign all assocs we return as
            # ourself. This is vaguely ok because there actually is only one
            # identity server, but it happens to have two names (matrix.org and
            # vector.im), and so we're not really lying too much.
            #
            # We do this when we return assocs, not when we receive them over
            # replication, so that we can undo this decision in the future if
            # we wish, without having destroyed the raw underlying data.
            sgassoc = signedjson.sign.sign_json(
                sgassoc,
                self.sydent.server_name,
                self.sydent.keyring.ed25519
            )
        return json.dumps(sgassoc)
예제 #40
0
    def render_GET(self, request):
        send_cors(request)

        err, args = get_args(request, ('token', 'sid', 'client_secret'))
        if err:
            msg = "Verification failed: Your request was invalid."
        else:
            resp = self.do_validate_request(args)
            if 'success' in resp and resp['success']:
                msg = "Verification successful! Please return to your Matrix client to continue."
                if 'next_link' in args:
                    next_link = args['next_link']
                    request.setResponseCode(302)
                    request.setHeader("Location", next_link)
            else:
                msg = "Verification failed: you may need to request another verification text"

        templateFile = self.sydent.cfg.get('http', 'verify_response_template')

        request.setHeader("Content-Type", "text/html")
        return open(templateFile).read() % {'message': msg}
예제 #41
0
    def render_POST(self, request):
        send_cors(request)
        err, args = get_args(request, ("private_key", "token", "mxid"))
        if err:
            return json.dumps(err)

        private_key_base64 = args['private_key']
        token = args['token']
        mxid = args['mxid']

        sender = self.tokenStore.getSenderForToken(token)
        if sender is None:
            request.setResponseCode(404)
            return json.dumps({
                "errcode": "M_UNRECOGNIZED",
                "error": "Didn't recognize token",
            })

        to_sign = {
            "mxid": mxid,
            "sender": sender,
            "token": token,
        }
        try:
            private_key = signedjson.key.decode_signing_key_base64(
                "ed25519",
                "0",
                private_key_base64
            )
            signed = signedjson.sign.sign_json(
                to_sign,
                self.server_name,
                private_key
            )
        except:
            return json.dumps({
                "errcode": "M_UNKNOWN",
            })

        return json.dumps(signed)
예제 #42
0
    def do_validate_request(self, request):
        send_cors(request)

        err, args = get_args(request, ('token', 'sid', 'client_secret'))
        if err:
            return err

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

        try:
            resp = self.sydent.validators.email.validateSessionWithToken(sid, clientSecret, tokenString)
        except IncorrectClientSecretException:
            return {'success': False, 'errcode': 'M_INCORRECT_CLIENT_SECRET',
                    '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"}

        if not resp:
            resp = {'success': False}

        return resp
예제 #43
0
    def render_POST(self, request):
        send_cors(request)
        err, args = get_args(request, ("medium", "address", "room_id", "sender",))
        if err:
            return json.dumps(err)
        medium = args["medium"]
        address = args["address"]
        roomId = args["room_id"]
        sender = args["sender"]

        globalAssocStore = GlobalAssociationStore(self.sydent)
        mxid = globalAssocStore.getMxid(medium, address)
        if mxid:
            request.setResponseCode(400)
            return json.dumps({
                "errcode": "THREEPID_IN_USE",
                "error": "Binding already known",
                "mxid": mxid,
            })

        if medium != "email":
            request.setResponseCode(400)
            return json.dumps({
                "errcode": "M_UNRECOGNIZED",
                "error": "Didn't understand medium '%s'" % (medium,),
            })

        token = self._randomString(128)

        tokenStore = JoinTokenStore(self.sydent)

        ephemeralPrivateKey = nacl.signing.SigningKey.generate()
        ephemeralPublicKey = ephemeralPrivateKey.verify_key

        ephemeralPrivateKeyBase64 = encode_base64(ephemeralPrivateKey.encode(), True)
        ephemeralPublicKeyBase64 = encode_base64(ephemeralPublicKey.encode(), True)

        tokenStore.storeEphemeralPublicKey(ephemeralPublicKeyBase64)
        tokenStore.storeToken(medium, address, roomId, sender, token)

        substitutions = {}
        for key, values in request.args.items():
            if len(values) == 1 and type(values[0]) == str:
                substitutions[key] = values[0]
        substitutions["token"] = token

        required = [
            'sender_display_name',
            'token',
            'room_name',
            'bracketed_room_name',
            'room_avatar_url',
            'sender_display_name',
            'guest_user_id',
            'guest_access_token',
        ]
        for k in required:
            substitutions.setdefault(k, '')

        substitutions["ephemeral_private_key"] = ephemeralPrivateKeyBase64
        if substitutions["room_name"] != '':
            substitutions["bracketed_room_name"] = "(%s)" % substitutions["room_name"]

        subject_header = Header(self.sydent.cfg.get('email', 'email.invite.subject', raw=True) % substitutions, 'utf8')
        substitutions["subject_header_value"] = subject_header.encode()

        sendEmail(self.sydent, "email.invite_template", address, substitutions)

        pubKey = self.sydent.keyring.ed25519.verify_key
        pubKeyBase64 = encode_base64(pubKey.encode())

        baseUrl = "%s/_matrix/identity/api/v1" % (self.sydent.cfg.get('http', 'client_http_base'),)

        keysToReturn = []
        keysToReturn.append({
            "public_key": pubKeyBase64,
            "key_validity_url": baseUrl + "/pubkey/isvalid",
        })
        keysToReturn.append({
            "public_key": ephemeralPublicKeyBase64,
            "key_validity_url": baseUrl + "/pubkey/ephemeral/isvalid",
        })

        resp = {
            "token": token,
            "public_key": pubKeyBase64,
            "public_keys": keysToReturn,
            "display_name": self.redact(address),
        }

        return json.dumps(resp)