def test_delete_on_bind(self): """Tests that 3PID invite tokens are deleted upon delivery after a successful bind. """ self.sydent.run() # The 3PID we're working with. medium = "email" address = "*****@*****.**" # Mock post_json_get_nothing so the /onBind call doesn't fail. async def post_json_get_nothing(uri, post_json, opts): return Response((b"HTTP", 1, 1), 200, b"OK", None, None) FederationHttpClient.post_json_get_nothing = Mock( side_effect=post_json_get_nothing, ) # Manually insert an invite token, we'll check later that it's been deleted. join_token_store = JoinTokenStore(self.sydent) join_token_store.storeToken( medium, address, "!someroom:example.com", "@jane:example.com", "sometoken", ) # Make sure the token still exists and can be retrieved. tokens = join_token_store.getTokens(medium, address) self.assertEqual(len(tokens), 1, tokens) # Bind the 3PID self.sydent.threepidBinder.addBinding( medium, address, "@john:example.com", ) # Give Sydent some time to call /onBind and delete the token. self.sydent.reactor.advance(1000) cur = self.sydent.db.cursor() # Manually retrieve the tokens for this 3PID. We don't use getTokens because it # filters out sent tokens, so would return nothing even if the token hasn't been # deleted. res = cur.execute( "SELECT medium, address, room_id, sender, token FROM invite_tokens" " WHERE medium = ? AND address = ?", ( medium, address, ), ) rows = res.fetchall() # Check that we didn't get any result. self.assertEqual(len(rows), 0, rows)
def _notify(self, assoc, attempt): """ Sends data about a new association (and, if necessary, the associated invites) to the associated MXID's homeserver. :param assoc: The association to send down to the homeserver. :type assoc: dict[str, any] :param attempt: The number of previous attempts to send this association. :type attempt: int """ mxid = assoc["mxid"] mxid_parts = mxid.split(":", 1) if len(mxid_parts) != 2: logger.error( "Can't notify on bind for unparseable mxid %s. Not retrying.", assoc["mxid"], ) return post_url = "matrix://%s/_matrix/federation/v1/3pid/onbind" % ( mxid_parts[1], ) logger.info("Making bind callback to: %s", post_url) # Make a POST to the chosen Synapse server http_client = FederationHttpClient(self.sydent) try: response = yield http_client.post_json_get_nothing( post_url, assoc, {}) except Exception as e: self._notifyErrback(assoc, attempt, e) return # If the request failed, try again with exponential backoff if response.code != 200: self._notifyErrback( assoc, attempt, "Non-OK error code received (%d)" % response.code) else: logger.info("Successfully notified on bind for %s" % (mxid, )) # Skip the deletion step if instructed so by the config. if not self.sydent.delete_tokens_on_bind: return # Only remove sent tokens when they've been successfully sent. try: joinTokenStore = JoinTokenStore(self.sydent) joinTokenStore.deleteTokens(assoc["medium"], assoc["address"]) logger.info( "Successfully deleted invite for %s from the store", assoc["address"], ) except Exception as e: logger.exception( "Couldn't remove invite for %s from the store", assoc["address"], )
def _notify(self, assoc, attempt): mxid = assoc["mxid"] mxid_parts = mxid.split(":", 1) if len(mxid_parts) != 2: logger.error( "Can't notify on bind for unparseable mxid %s. Not retrying.", assoc["mxid"], ) return post_url = "matrix://%s/_matrix/federation/v1/3pid/onbind" % ( mxid_parts[1], ) logger.info("Making bind callback to: %s", post_url) # Make a POST to the chosen Synapse server http_client = FederationHttpClient(self.sydent) try: response = yield http_client.post_json_get_nothing( post_url, assoc, {}) except Exception as e: self._notifyErrback(assoc, attempt, e) return # If the request failed, try again with exponential backoff if response.code != 200: self._notifyErrback( assoc, attempt, "Non-OK error code received (%d)" % response.code) else: logger.info("Successfully notified on bind for %s" % (mxid, )) # Only remove sent tokens when they've been successfully sent. try: joinTokenStore = JoinTokenStore(self.sydent) joinTokenStore.deleteTokens(assoc["medium"], assoc["address"]) logger.info( "Successfully deleted invite for %s from the store", assoc["address"], ) except Exception as e: logger.exception( "Couldn't remove invite for %s from the store", assoc["address"], )
def addBinding(self, medium, address, mxid): """Binds the given 3pid to the given mxid. It's assumed that we have somehow validated that the given user owns the given 3pid Args: medium (str): the type of 3pid address (str): the 3pid mxid (str): the mxid to bind it to """ localAssocStore = LocalAssociationStore(self.sydent) createdAt = time_msec() expires = createdAt + ThreepidBinder.THREEPID_ASSOCIATION_LIFETIME_MS assoc = ThreepidAssociation(medium, address, mxid, createdAt, createdAt, expires) localAssocStore.addOrUpdateAssociation(assoc) self.sydent.pusher.doLocalPush() joinTokenStore = JoinTokenStore(self.sydent) pendingJoinTokens = joinTokenStore.getTokens(medium, 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(medium, address) signer = Signer(self.sydent) sgassoc = signer.signedThreePidAssociation(assoc) self._notify(sgassoc, 0) return sgassoc
def addBinding(self, medium, address, mxid): """Binds the given 3pid to the given mxid. It's assumed that we have somehow validated that the given user owns the given 3pid Args: medium (str): the type of 3pid address (str): the 3pid mxid (str): the mxid to bind it to """ localAssocStore = LocalAssociationStore(self.sydent) createdAt = time_msec() expires = createdAt + ThreepidBinder.THREEPID_ASSOCIATION_LIFETIME_MS assoc = ThreepidAssociation(medium, address, mxid, createdAt, createdAt, expires) localAssocStore.addOrUpdateAssociation(assoc) self.sydent.pusher.doLocalPush() joinTokenStore = JoinTokenStore(self.sydent) pendingJoinTokens = joinTokenStore.getTokens(medium, 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(medium, address) signer = Signer(self.sydent) sgassoc = signer.signedThreePidAssociation(assoc) self._notify(sgassoc, 0) return sgassoc
class BlindlySignStuffServlet(Resource): isLeaf = True def __init__(self, syd): self.server_name = syd.server_name self.tokenStore = JoinTokenStore(syd) 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) @jsonwrap def render_OPTIONS(self, request): send_cors(request) request.setResponseCode(200) return {}
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
class EphemeralPubkeyIsValidServlet(Resource): isLeaf = True def __init__(self, syd): self.joinTokenStore = JoinTokenStore(syd) @jsonwrap def render_GET(self, request): args = get_args(request, ("public_key",)) publicKey = args["public_key"] return { 'valid': self.joinTokenStore.validateEphemeralPublicKey(publicKey), }
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
class EphemeralPubkeyIsValidServlet(Resource): isLeaf = True def __init__(self, syd): self.joinTokenStore = JoinTokenStore(syd) 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), })
class EphemeralPubkeyIsValidServlet(Resource): isLeaf = True def __init__(self, syd): self.joinTokenStore = JoinTokenStore(syd) def render_GET(self, request): err = require_args(request, ("public_key", )) if err: return json.dumps(err) publicKey = request.args["public_key"][0] return json.dumps({ 'valid': self.joinTokenStore.validateEphemeralPublicKey(publicKey), })
class BlindlySignStuffServlet(Resource): isLeaf = True def __init__(self, syd: "Sydent", require_auth: bool = False) -> None: self.sydent = syd self.server_name = syd.config.general.server_name self.tokenStore = JoinTokenStore(syd) self.require_auth = require_auth @jsonwrap 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_OPTIONS(self, request: Request) -> bytes: send_cors(request) return b""
class BlindlySignStuffServlet(Resource): isLeaf = True def __init__(self, syd): self.server_name = syd.server_name self.tokenStore = JoinTokenStore(syd) 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) @jsonwrap def render_OPTIONS(self, request): send_cors(request) request.setResponseCode(200) return {}
class BlindlySignStuffServlet(Resource): isLeaf = True def __init__(self, syd): self.sydent = syd self.server_name = syd.server_name self.tokenStore = JoinTokenStore(syd) @jsonwrap 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 def render_OPTIONS(self, request): send_cors(request) request.setResponseCode(200) return b''
def __init__(self, syd): self.joinTokenStore = JoinTokenStore(syd)
def addBinding(self, medium: str, address: str, mxid: str) -> Dict[str, Any]: """ Binds the given 3pid to the given mxid. It's assumed that we have somehow validated that the given user owns the given 3pid :param medium: The medium of the 3PID to bind. :param address: The address of the 3PID to bind. :param mxid: The MXID to bind the 3PID to. :return: The signed association. """ # ensure we casefold email address before storing normalised_address = normalise_address(address, medium) localAssocStore = LocalAssociationStore(self.sydent) # Fill out the association details createdAt = time_msec() expires = createdAt + ThreepidBinder.THREEPID_ASSOCIATION_LIFETIME_MS # Hash the medium + address and store that hash for the purposes of # later lookups lookup_pepper = self.hashing_store.get_lookup_pepper() assert lookup_pepper is not None str_to_hash = " ".join([normalised_address, medium, lookup_pepper], ) lookup_hash = sha256_and_url_safe_base64(str_to_hash) assoc = ThreepidAssociation( medium, normalised_address, lookup_hash, mxid, createdAt, createdAt, expires, ) localAssocStore.addOrUpdateAssociation(assoc) self.sydent.pusher.doLocalPush() joinTokenStore = JoinTokenStore(self.sydent) pendingJoinTokens = joinTokenStore.getTokens(medium, normalised_address) invites = [] # Widen the value type to Any: we're going to set the signed key # to point to a dict, but pendingJoinTokens yields Dict[str, str] token: Dict[str, Any] for token in pendingJoinTokens: token["mxid"] = mxid presigned = { "mxid": mxid, "token": token["token"], } token["signed"] = signedjson.sign.sign_json( presigned, self.sydent.config.general.server_name, self.sydent.keyring.ed25519, ) invites.append(token) if invites: assoc.extra_fields["invites"] = invites joinTokenStore.markTokensAsSent(medium, normalised_address) signer = Signer(self.sydent) sgassoc = signer.signedThreePidAssociation(assoc) defer.ensureDeferred(self._notify(sgassoc, 0)) return sgassoc
def render_POST(self, request): send_cors(request) err = require_args(request, ("medium", "address", "room_id", "sender",)) if err: return json.dumps(err) medium = request.args["medium"][0] address = request.args["address"][0] roomId = request.args["room_id"][0] sender = request.args["sender"][0] 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)
def __init__(self, syd: "Sydent") -> None: self.joinTokenStore = JoinTokenStore(syd)
def __init__(self, syd): self.server_name = syd.server_name self.tokenStore = JoinTokenStore(syd)
def render_POST(self, request: Request) -> JsonDict: send_cors(request) args = get_args( request, ( "medium", "address", "room_id", "sender", ), ) medium = args["medium"] address = args["address"] roomId = args["room_id"] sender = args["sender"] # ensure we are casefolding email address before storing normalised_address = normalise_address(address, medium) verified_sender = None if self.require_auth: account = authV2(self.sydent, request) verified_sender = sender if account.userId != sender: raise MatrixRestError(403, "M_UNAUTHORIZED", "'sender' doesn't match") globalAssocStore = GlobalAssociationStore(self.sydent) mxid = globalAssocStore.getMxid(medium, normalised_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, ), } if not (0 < len(address) <= MAX_EMAIL_ADDRESS_LENGTH): request.setResponseCode(400) return { "errcode": "M_INVALID_PARAM", "error": "Invalid email provided" } 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, normalised_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, str): 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, "") # For MSC3288 room type, prefer the stable field, but fallback to the # unstable field. if "room_type" not in substitutions: substitutions["room_type"] = substitutions.get( "org.matrix.msc3288.room_type", "") substitutions["bracketed_verified_sender"] = "" if verified_sender: substitutions["bracketed_verified_sender"] = "(%s) " % ( verified_sender, ) substitutions["ephemeral_private_key"] = ephemeralPrivateKeyBase64 if substitutions["room_name"] != "": substitutions[ "bracketed_room_name"] = "(%s) " % substitutions["room_name"] substitutions[ "web_client_location"] = self.sydent.config.email.default_web_client_location if "org.matrix.web_client_location" in substitutions: substitutions["web_client_location"] = substitutions[ "org.matrix.web_client_location"] if substitutions["room_type"] == "m.space": subject = self.sydent.config.email.invite_subject_space % substitutions else: subject = self.sydent.config.email.invite_subject % substitutions substitutions["subject_header_value"] = Header(subject, "utf8").encode() brand = self.sydent.brand_from_request(request) # self.sydent.config.email.invite_template is deprecated if self.sydent.config.email.invite_template is None: templateFile = self.sydent.get_branded_template( brand, "invite_template.eml", ) else: templateFile = self.sydent.config.email.invite_template try: sendEmail(self.sydent, templateFile, normalised_address, substitutions) except EmailAddressException: request.setResponseCode(HTTPStatus.BAD_REQUEST) return { "errcode": "M_INVALID_EMAIL", "error": "Invalid email address" } pubKey = self.sydent.keyring.ed25519.verify_key pubKeyBase64 = encode_base64(pubKey.encode()) baseUrl = "%s/_matrix/identity/api/v1" % ( self.sydent.config.http.server_http_url_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
def __init__(self, syd: "Sydent", require_auth: bool = False) -> None: self.sydent = syd self.server_name = syd.config.general.server_name self.tokenStore = JoinTokenStore(syd) self.require_auth = require_auth
def addBinding(self, medium, address, mxid): """ Binds the given 3pid to the given mxid. It's assumed that we have somehow validated that the given user owns the given 3pid :param medium: The medium of the 3PID to bind. :type medium: unicode :param address: The address of the 3PID to bind. :type address: unicode :param mxid: The MXID to bind the 3PID to. :type mxid: unicode :return: The signed association. :rtype: dict[str, any] """ localAssocStore = LocalAssociationStore(self.sydent) # Fill out the association details createdAt = time_msec() expires = createdAt + ThreepidBinder.THREEPID_ASSOCIATION_LIFETIME_MS # Hash the medium + address and store that hash for the purposes of # later lookups str_to_hash = u' '.join( [address, medium, self.hashing_store.get_lookup_pepper()], ) lookup_hash = sha256_and_url_safe_base64(str_to_hash) assoc = ThreepidAssociation( medium, address, lookup_hash, mxid, createdAt, createdAt, expires, ) localAssocStore.addOrUpdateAssociation(assoc) self.sydent.pusher.doLocalPush() joinTokenStore = JoinTokenStore(self.sydent) pendingJoinTokens = joinTokenStore.getTokens(medium, 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(medium, address) signer = Signer(self.sydent) sgassoc = signer.signedThreePidAssociation(assoc) self._notify(sgassoc, 0) return sgassoc
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)
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