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