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
def render_POST(self, request): send_cors(request) err, args = get_args(request, ('sid', 'client_secret', 'mxid')) if err: return err sid = args['sid'] mxid = args['mxid'] clientSecret = args['client_secret'] # Return the same error for not found / bad client secret otherwise people can get information about # sessions without knowing the secret noMatchError = {'errcode': 'M_NO_VALID_SESSION', 'error': "No valid session was found matching that sid and client secret"} try: valSessionStore = ThreePidValSessionStore(self.sydent) s = valSessionStore.getValidatedSession(sid, clientSecret) except IncorrectClientSecretException: return noMatchError except SessionExpiredException: return {'errcode': 'M_SESSION_EXPIRED', 'error': "This validation session has expired: call requestToken again"} except InvalidSessionIdException: return noMatchError except SessionNotValidatedException: return {'errcode': 'M_SESSION_NOT_VALIDATED', 'error': "This validation session has not yet been completed"} res = self.sydent.threepidBinder.addBinding(s.medium, s.address, mxid) return res
def render_GET(self, request: Request) -> JsonDict: args = get_args(request, ("public_key", )) publicKey = args["public_key"] return { "valid": self.joinTokenStore.validateEphemeralPublicKey(publicKey), }
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}
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
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}
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 {}
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)
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)
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}
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 })
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}
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)
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
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
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, })
def render_GET(self, request): err, args = get_args(request, ('sid', 'client_secret')) if err: return err sid = args['sid'] clientSecret = args['client_secret'] valSessionStore = ThreePidValSessionStore(self.sydent) noMatchError = {'errcode': 'M_NO_VALID_SESSION', 'error': "No valid session was found matching that sid and client secret"} try: s = valSessionStore.getValidatedSession(sid, clientSecret) except IncorrectClientSecretException: return noMatchError except SessionExpiredException: return {'errcode': 'M_SESSION_EXPIRED', 'error': "This validation session has expired: call requestToken again"} except InvalidSessionIdException: return noMatchError except SessionNotValidatedException: return {'errcode': 'M_SESSION_NOT_VALIDATED', 'error': "This validation session has not yet been completed"} return { 'medium': s.medium, 'address': s.address, 'validated_at': s.mtime }
def render_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")
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
def render_GET(self, request): args = get_args(request, ("public_key",)) publicKey = args["public_key"] return { 'valid': self.joinTokenStore.validateEphemeralPublicKey(publicKey), }
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" }
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'], )
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)
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), })
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", }
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})
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"], )
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
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'], )
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), })
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 {}
def render_POST(self, request: Request) -> JsonDict: send_cors(request) account = None if self.require_auth: account = authV2(self.sydent, request) args = get_args(request, ("sid", "client_secret", "mxid")) sid = args["sid"] mxid = args["mxid"] clientSecret = args["client_secret"] if not is_valid_client_secret(clientSecret): raise MatrixRestError(400, "M_INVALID_PARAM", "Invalid client_secret provided") if account: # This is a v2 API so only allow binding to the logged in user id if account.userId != mxid: raise MatrixRestError( 403, "M_UNAUTHORIZED", "This user is prohibited from binding to the mxid", ) try: valSessionStore = ThreePidValSessionStore(self.sydent) s = valSessionStore.getValidatedSession(sid, clientSecret) except (IncorrectClientSecretException, InvalidSessionIdException): # Return the same error for not found / bad client secret otherwise # people can get information about sessions without knowing the # secret. raise MatrixRestError( 404, "M_NO_VALID_SESSION", "No valid session was found matching that sid and client secret", ) except SessionExpiredException: raise MatrixRestError( 400, "M_SESSION_EXPIRED", "This validation session has expired: call requestToken again", ) except SessionNotValidatedException: raise MatrixRestError( 400, "M_SESSION_NOT_VALIDATED", "This validation session has not yet been completed", ) res = self.sydent.threepidBinder.addBinding(s.medium, s.address, mxid) return res
def render_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
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
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)
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}
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)
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
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)