Esempio n. 1
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 }
Esempio n. 2
0
    def requestToken(self, phoneNumber, clientSecret, sendAttempt, nextLink):
        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
Esempio n. 3
0
    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_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
Esempio n. 4
0
    async def requestToken(
        self,
        phoneNumber: phonenumbers.PhoneNumber,
        clientSecret: str,
        send_attempt: int,
        brand: Optional[str] = None,
    ) -> int:
        """
        Creates or retrieves a validation session and sends an text message to the
        corresponding phone number address with a token to use to verify the association.

        :param phoneNumber: The phone number to send the email to.
        :param clientSecret: The client secret to use.
        :param send_attempt: The current send attempt.
        :param brand: A hint at a brand from the request.

        :return: The ID of the session created (or of the existing one if any)
        """
        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, token_info = valSessionStore.getOrCreateTokenSession(
            medium="msisdn", address=msisdn, clientSecret=clientSecret)

        valSessionStore.setMtime(valSession.id, time_msec())

        if token_info.send_attempt_number >= send_attempt:
            logger.info(
                "Not texting code because current send attempt (%d) is not less than given send attempt (%s)",
                send_attempt,
                token_info.send_attempt_number,
            )
            return valSession.id

        smsBodyTemplate = self.sydent.config.sms.body_template
        originator = self.getOriginator(phoneNumber)

        logger.info(
            "Attempting to text code %s to %s (country %d) with originator %s",
            token_info.token,
            msisdn,
            phoneNumber.country_code,
            originator,
        )

        smsBody = smsBodyTemplate.format(token=token_info.token)

        await self.omSms.sendTextSMS(smsBody, msisdn, originator)

        valSessionStore.setSendAttemptNumber(valSession.id, send_attempt)

        return valSession.id
Esempio n. 5
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. 6
0
    def requestToken(self,
                     emailAddress,
                     clientSecret,
                     sendAttempt,
                     nextLink,
                     ipaddress=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

        :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())

        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
Esempio n. 7
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
Esempio n. 8
0
    def requestToken(self, phoneNumber, clientSecret, sendAttempt, brand=None):
        """
        Creates or retrieves a validation session and sends an text message to the
        corresponding phone number address with a token to use to verify the association.

        :param phoneNumber: The phone number to send the email to.
        :type phoneNumber: phonenumbers.PhoneNumber
        :param clientSecret: The client secret to use.
        :type clientSecret: unicode
        :param sendAttempt: The current send attempt.
        :type sendAttempt: int
        :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
        """
        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 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. 10
0
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.

    :param sid: The ID of the session to validate.
    :type sid: unicode
    :param clientSecret: The client secret to validate.
    :type clientSecret: unicode
    :param token: The token to validate.
    :type token: unicode

    :return: A dict with a "success" key which is True if the session
        was successfully validated, False otherwise.
    :rtype: dict[str, bool]

    :raise IncorrectClientSecretException: The provided client_secret is incorrect.
    :raise SessionExpiredException: The session has expired.
    :raise InvalidSessionIdException: The session ID couldn't be matched with an
        existing session.
    :raise IncorrectSessionTokenException: The provided token is incorrect
    """
    valSessionStore = ThreePidValSessionStore(sydent)
    s = valSessionStore.getTokenSessionById(sid)
    if not s:
        logger.info("Session ID %s not found", sid)
        raise InvalidSessionIdException()

    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")
        raise IncorrectSessionTokenException()
    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
        }
Esempio n. 12
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. 13
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
Esempio n. 14
0
    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
Esempio n. 15
0
    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
Esempio n. 16
0
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
Esempio n. 17
0
    def run(self) -> None:
        self.clientApiHttpServer.setup()
        self.replicationHttpsServer.setup()
        self.pusher.setup()
        self.maybe_start_prometheus_server()

        # 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)

        if self.config.http.internal_port is not None:
            internalport = self.config.http.internal_port
            interface = self.config.http.internal_bind_address

            self.internalApiHttpServer = InternalApiHttpServer(self)
            self.internalApiHttpServer.setup(interface, internalport)

        if self.config.general.pidfile:
            with open(self.config.general.pidfile, "w") as pidfile:
                pidfile.write(str(os.getpid()) + "\n")

        self.reactor.run()
Esempio n. 18
0
    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)
Esempio n. 19
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. 20
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()