コード例 #1
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)
コード例 #2
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}
コード例 #3
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
コード例 #4
0
    def render_POST(self, request):
        send_cors(request)

        authIfV2(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 = signedjson.sign.sign_json(to_sign, self.server_name,
                                               private_key)
        except:
            logger.exception("signing failed")
            raise MatrixRestError(500, "M_UNKNOWN", "Internal Server Error")

        return signed
コード例 #5
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)
コード例 #6
0
    def render_POST(self, request):
        """
        Invalidate the given access token
        """
        send_cors(request)

        authIfV2(self.sydent, request, False)

        token = tokenFromRequest(request)

        accountStore = AccountStore(self.sydent)
        accountStore.delToken(token)
        return {}
コード例 #7
0
    def render_GET(self, request):
        """
        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)

        authIfV2(self.sydent, request)

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

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

        globalAssocStore = GlobalAssociationStore(self.sydent)

        sgassoc = globalAssocStore.signedAssociationStringForThreepid(medium, address)

        if not sgassoc:
            return {}

        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 sgassoc
コード例 #8
0
    def render_POST(self, request):
        send_cors(request)

        authIfV2(self.sydent, request)

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

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

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

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

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

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

        return resp
コード例 #9
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
        }
コード例 #10
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 {}
コード例 #11
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
コード例 #12
0
    def render_GET(self, request):
        """
        Return the hashing algorithms and pepper that this IS supports. The
        pepper included in the response is stored in the database, or
        otherwise generated.

        Returns: An object containing an array of hashing algorithms the
                 server supports, and a `lookup_pepper` field, which is a
                 server-defined value that the client should include in the 3PID
                 information before hashing.
        """
        send_cors(request)

        authIfV2(self.sydent, request)

        request.setResponseCode(200)
        return {
            "algorithms": self.known_algorithms,
            "lookup_pepper": self.lookup_pepper,
        }
コード例 #13
0
    def render_GET(self, request):
        """
        Return information about the user's account
        (essentially just a 'who am i')
        """
        send_cors(request)

        account = authIfV2(self.sydent, request)

        return {
            "user_id": account.userId,
        }
コード例 #14
0
    def render_POST(self, request):
        authIfV2(self.sydent, request)

        return self.do_validate_request(request)
コード例 #15
0
    def render_POST(self, request):
        """
        Perform lookups with potentially hashed 3PID details.

        Depending on our response to /hash_details, the client will choose a
        hash algorithm and pepper, hash the 3PIDs it wants to lookup, and
        send them to us, along with the algorithm and pepper it used.

        We first check this algorithm/pepper combo matches what we expect,
        then compare the 3PID details to what we have in the database.

        Params: A JSON object containing the following keys:
                * 'addresses': List of hashed/plaintext (depending on the
                               algorithm) 3PID addresses and mediums.
                * 'algorithm': The algorithm the client has used to process
                               the 3PIDs.
                * 'pepper': The pepper the client has attached to the 3PIDs.

        Returns: Object with key 'mappings', which is a dictionary of results
                 where each result is a key/value pair of what the client sent, and
                 the matching Matrix User ID that claims to own that 3PID.

                 User IDs for which no mapping is found are omitted.
        """
        send_cors(request)

        authIfV2(self.sydent, request)

        args = get_args(request, ('addresses', 'algorithm', 'pepper'))

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

        algorithm = str(args['algorithm'])
        if algorithm not in HashDetailsServlet.known_algorithms:
            request.setResponseCode(400)
            return {'errcode': 'M_INVALID_PARAM', 'error': 'algorithm is not supported'}

        # Ensure address count is under the configured limit
        limit = int(self.sydent.cfg.get("general", "address_lookup_limit"))
        if len(addresses) > limit:
            request.setResponseCode(400)
            return {'errcode': 'M_TOO_LARGE', 'error': 'More than the maximum amount of '
                                                       'addresses provided'}

        pepper = str(args['pepper'])
        if pepper != self.lookup_pepper:
            request.setResponseCode(400)
            return {
                'errcode': 'M_INVALID_PEPPER',
                'error': "pepper does not match '%s'" % (self.lookup_pepper,),
                'algorithm': algorithm,
                'lookup_pepper': self.lookup_pepper,
            }

        logger.info("Lookup of %d threepid(s) with algorithm %s", len(addresses), algorithm)
        if algorithm == "none":
            # Lookup without hashing
            medium_address_tuples = []
            for address_and_medium in addresses:
                # Parse medium, address components
                address_medium_split = address_and_medium.split()

                # Forbid addresses that contain a space
                if len(address_medium_split) != 2:
                    request.setResponseCode(400)
                    return {
                        'errcode': 'M_UNKNOWN',
                        'error': 'Invalid "address medium" pair: "%s"' % address_and_medium
                    }

                # Get the mxid for the address/medium combo if known
                address, medium = address_medium_split
                medium_address_tuples.append((medium, address))

            # Lookup the mxids
            medium_address_mxid_tuples = self.globalAssociationStore.getMxids(medium_address_tuples)

            # Return a dictionary of lookup_string: mxid values
            return {'mappings': {"%s %s" % (x[1], x[0]): x[2]
                                 for x in medium_address_mxid_tuples}}

        elif algorithm == "sha256":
            # Lookup using SHA256 with URL-safe base64 encoding
            mappings = self.globalAssociationStore.retrieveMxidsForHashes(addresses)

            return {'mappings': mappings}

        request.setResponseCode(400)
        return {'errcode': 'M_INVALID_PARAM', 'error': 'algorithm is not supported'}
コード例 #16
0
    def render_POST(self, request):
        send_cors(request)

        authIfV2(self.sydent, request)

        args = get_args(request, ("medium", "address", "room_id", "sender",))
        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 {
                "errcode": "M_THREEPID_IN_USE",
                "error": "Binding already known",
                "mxid": mxid,
            }

        if medium != "email":
            request.setResponseCode(400)
            return {
                "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)

        # Variables to substitute in the template.
        substitutions = {}
        # Include all arguments sent via the request.
        for k, v in args.items():
            if isinstance(v, string_types):
                substitutions[k] = v
        substitutions["token"] = token

        # Substitutions that the template requires, but are optional to provide
        # to the API.
        extra_substitutions = [
            'sender_display_name',
            'token',
            'room_name',
            'bracketed_room_name',
            'room_avatar_url',
            'sender_avatar_url',
            'guest_user_id',
            'guest_access_token',
        ]
        for k in extra_substitutions:
            substitutions.setdefault(k, '')

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

        substitutions["web_client_location"] = self.sydent.default_web_client_location
        if 'org.matrix.web_client_location' in substitutions:
            substitutions["web_client_location"] = substitutions.pop("org.matrix.web_client_location")

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

        brand = self.sydent.brand_from_request(request)
        templateFile = self.sydent.get_branded_template(
            brand,
            "invite_template.eml",
            ('email', 'email.invite_template'),
        )

        sendEmail(self.sydent, templateFile, 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_email_address(address),
        }

        return resp