def pushUpdates(self, sgAssocs): globalAssocStore = GlobalAssociationStore(self.sydent) for localId in sgAssocs: if localId > self.lastId: assocObj = threePidAssocFromDict(sgAssocs[localId]) if assocObj.mxid is not None: # Assign a lookup_hash to this association str_to_hash = ' '.join([ assocObj.address, assocObj.medium, self.hashing_store.get_lookup_pepper() ], ) assocObj.lookup_hash = sha256_and_url_safe_base64( str_to_hash) # We can probably skip verification for the local peer (although it could # be good as a sanity check) globalAssocStore.addAssociation( assocObj, json.dumps(sgAssocs[localId]), self.sydent.server_name, localId) else: globalAssocStore.removeAssociation(assocObj.medium, assocObj.address) d = defer.succeed(True) return d
def pushUpdates(self, sgAssocs): globalAssocStore = GlobalAssociationStore(self.sydent) for localId in sgAssocs: if localId > self.lastId: assocObj = threePidAssocFromDict(sgAssocs[localId]) # We can probably skip verification for the local peer (although it could be good as a sanity check) globalAssocStore.addAssociation(assocObj, json.dumps(sgAssocs[localId]), self.sydent.server_name, localId) d = twisted.internet.defer.succeed(True) return d
def pushUpdates(self, sgAssocs): globalAssocStore = GlobalAssociationStore(self.sydent) for localId in sgAssocs: if localId > self.lastId: assocObj = threePidAssocFromDict(sgAssocs[localId]) if assocObj.mxid is not None: # We can probably skip verification for the local peer (although it could be good as a sanity check) globalAssocStore.addAssociation(assocObj, json.dumps(sgAssocs[localId]), self.sydent.server_name, localId) else: globalAssocStore.removeAssociation(assocObj.medium, assocObj.address) d = defer.succeed(True) return d
def pushUpdates(self, sgAssocs): globalAssocStore = GlobalAssociationStore(self.sydent) for localId in sgAssocs: if localId > self.lastId: assocObj = threePidAssocFromDict(sgAssocs[localId]) if assocObj.mxid is not None: # We can probably skip verification for the local peer (although it could be good as a sanity check) globalAssocStore.addAssociation( assocObj, json.dumps(sgAssocs[localId]), self.sydent.server_name, localId) else: globalAssocStore.removeAssociation(assocObj.medium, assocObj.address) d = defer.succeed(True) return d
def pushUpdates(self, sgAssocs: SignedAssociations) -> "Deferred[bool]": """ Saves the given associations in the global associations store. Only stores an association if its ID is greater than the last seen ID. :param sgAssocs: The associations to save. :return: A deferred that succeeds with the value `True`. """ globalAssocStore = GlobalAssociationStore(self.sydent) for localId in sgAssocs: if localId > self.lastId: assocObj = threePidAssocFromDict(sgAssocs[localId]) # ensure we are casefolding email addresses assocObj.address = normalise_address(assocObj.address, assocObj.medium) if assocObj.mxid is not None: # Assign a lookup_hash to this association pepper = self.hashing_store.get_lookup_pepper() if not pepper: raise RuntimeError("No lookup_pepper in the database.") str_to_hash = " ".join([ assocObj.address, assocObj.medium, pepper, ], ) assocObj.lookup_hash = sha256_and_url_safe_base64( str_to_hash) # We can probably skip verification for the local peer (although it could # be good as a sanity check) globalAssocStore.addAssociation( assocObj, json.dumps(sgAssocs[localId]), self.sydent.config.general.server_name, localId, ) else: globalAssocStore.removeAssociation(assocObj.medium, assocObj.address) d = defer.succeed(True) return d
def pushUpdates(self, sgAssocs): """ Saves the given associations in the global associations store. Only stores an association if its ID is greater than the last seen ID. :param sgAssocs: The associations to save. :type sgAssocs: dict[int, dict[str, any]] :return: True :rtype: twisted.internet.defer.Deferred[bool] """ globalAssocStore = GlobalAssociationStore(self.sydent) for localId in sgAssocs: if localId > self.lastId: assocObj = threePidAssocFromDict(sgAssocs[localId]) if assocObj.mxid is not None: # Assign a lookup_hash to this association str_to_hash = u' '.join([ assocObj.address, assocObj.medium, self.hashing_store.get_lookup_pepper() ], ) assocObj.lookup_hash = sha256_and_url_safe_base64( str_to_hash) # We can probably skip verification for the local peer (although it could # be good as a sanity check) globalAssocStore.addAssociation( assocObj, json.dumps(sgAssocs[localId]), self.sydent.server_name, localId) else: globalAssocStore.removeAssociation(assocObj.medium, assocObj.address) d = defer.succeed(True) return d
def render_POST(self, request): peerCert = request.transport.getPeerCertificate() peerCertCn = peerCert.get_subject().commonName peerStore = PeerStore(self.sydent) peer = peerStore.getPeerByName(peerCertCn) if not peer: logger.warn( "Got connection from %s but no peer found by that name", peerCertCn) request.setResponseCode(403) return { 'errcode': 'M_UNKNOWN_PEER', 'error': 'This peer is not known to this server' } logger.info("Push connection made from peer %s", peer.servername) if not request.requestHeaders.hasHeader('Content-Type') or \ request.requestHeaders.getRawHeaders('Content-Type')[0] != 'application/json': logger.warn( "Peer %s made push connection with non-JSON content (type: %s)", peer.servername, request.requestHeaders.getRawHeaders('Content-Type')[0]) return { 'errcode': 'M_NOT_JSON', 'error': 'This endpoint expects JSON' } try: inJson = json.load(request.content) except ValueError: logger.warn("Peer %s made push connection with malformed JSON", peer.servername) return {'errcode': 'M_BAD_JSON', 'error': 'Malformed JSON'} if 'sgAssocs' not in inJson: logger.warn( "Peer %s made push connection with no 'sgAssocs' key in JSON", peer.servername) return { 'errcode': 'M_BAD_JSON', 'error': 'No "sgAssocs" key in JSON' } failedIds = [] globalAssocsStore = GlobalAssociationStore(self.sydent) for originId, sgAssoc in inJson['sgAssocs'].items(): try: peer.verifyMessage(sgAssoc) logger.debug( "Signed association from %s with origin ID %s verified", peer.servername, originId) # Don't bother adding if one has already failed: we add all of them or none so we're only going to # roll back the transaction anyway (but we continue to try & verify the rest so we can give a # complete list of the ones that don't verify) if len(failedIds) > 0: continue assocObj = threePidAssocFromDict(sgAssoc) if assocObj.mxid is not None: globalAssocsStore.addAssociation(assocObj, json.dumps(sgAssoc), peer.servername, originId, commit=False) else: logger.info( "Incoming deletion: removing associations for %s / %s", assocObj.medium, assocObj.address) globalAssocsStore.removeAssociation( assocObj.medium, assocObj.address) logger.info("Stored association origin ID %s from %s", originId, peer.servername) except: failedIds.append(originId) logger.warn( "Failed to verify signed association from %s with origin ID %s", peer.servername, originId) twisted.python.log.err() if len(failedIds) > 0: self.sydent.db.rollback() request.setResponseCode(400) return { 'errcode': 'M_VERIFICATION_FAILED', 'error': 'Verification failed for one or more associations', 'failed_ids': failedIds } else: self.sydent.db.commit() return {'success': True}
def render_POST(self, request): peerCert = request.transport.getPeerCertificate() peerCertCn = peerCert.get_subject().commonName peerStore = PeerStore(self.sydent) peer = peerStore.getPeerByName(peerCertCn) if not peer: logger.warn( "Got connection from %s but no peer found by that name", peerCertCn) raise MatrixRestError(403, 'M_UNKNOWN_PEER', 'This peer is not known to this server') logger.info("Push connection made from peer %s", peer.servername) if not request.requestHeaders.hasHeader('Content-Type') or \ request.requestHeaders.getRawHeaders('Content-Type')[0] != 'application/json': logger.warn( "Peer %s made push connection with non-JSON content (type: %s)", peer.servername, request.requestHeaders.getRawHeaders('Content-Type')[0]) raise MatrixRestError(400, 'M_NOT_JSON', 'This endpoint expects JSON') try: # json.loads doesn't allow bytes in Python 3.5 inJson = json.loads(request.content.read().decode("UTF-8")) except ValueError: logger.warn("Peer %s made push connection with malformed JSON", peer.servername) raise MatrixRestError(400, 'M_BAD_JSON', 'Malformed JSON') if 'sgAssocs' not in inJson: logger.warn( "Peer %s made push connection with no 'sgAssocs' key in JSON", peer.servername) raise MatrixRestError(400, 'M_BAD_JSON', 'No "sgAssocs" key in JSON') failedIds = [] globalAssocsStore = GlobalAssociationStore(self.sydent) # Ensure items are pulled out of the dictionary in order of origin_id. sg_assocs = inJson.get('sgAssocs', {}) sg_assocs = sorted(sg_assocs.items(), key=lambda k: int(k[0])) for originId, sgAssoc in sg_assocs: try: peer.verifySignedAssociation(sgAssoc) logger.debug( "Signed association from %s with origin ID %s verified", peer.servername, originId) # Don't bother adding if one has already failed: we add all of them or none so # we're only going to roll back the transaction anyway (but we continue to try # & verify the rest so we can give a complete list of the ones that don't # verify) if len(failedIds) > 0: continue assocObj = threePidAssocFromDict(sgAssoc) if assocObj.mxid is not None: # Calculate the lookup hash with our own pepper for this association str_to_hash = u' '.join([ assocObj.address, assocObj.medium, self.hashing_store.get_lookup_pepper() ], ) assocObj.lookup_hash = sha256_and_url_safe_base64( str_to_hash) # Add this association globalAssocsStore.addAssociation(assocObj, json.dumps(sgAssoc), peer.servername, originId, commit=False) else: logger.info( "Incoming deletion: removing associations for %s / %s", assocObj.medium, assocObj.address) globalAssocsStore.removeAssociation( assocObj.medium, assocObj.address) logger.info("Stored association origin ID %s from %s", originId, peer.servername) except: failedIds.append(originId) logger.warn( "Failed to verify signed association from %s with origin ID %s", peer.servername, originId) twisted.python.log.err() if len(failedIds) > 0: self.sydent.db.rollback() request.setResponseCode(400) return { 'errcode': 'M_VERIFICATION_FAILED', 'error': 'Verification failed for one or more associations', 'failed_ids': failedIds } else: self.sydent.db.commit() return {'success': True}
def render_POST(self, request: Request) -> JsonDict: # Cast safety: This request has an ISSLTransport because this servlet # is a resource under the ReplicationHttpsServer and nowhere else. request.transport = cast(ISSLTransport, request.transport) peerCert = cast(X509, request.transport.getPeerCertificate()) peerCertCn = peerCert.get_subject().commonName peerStore = PeerStore(self.sydent) peer = peerStore.getPeerByName(peerCertCn) if not peer: logger.warning( "Got connection from %s but no peer found by that name", peerCertCn) raise MatrixRestError(403, "M_UNKNOWN_PEER", "This peer is not known to this server") logger.info("Push connection made from peer %s", peer.servername) if (not request.requestHeaders.hasHeader("Content-Type") # Type safety: the hasHeader call returned True, so getRawHeaders() # returns a nonempty list. or request.requestHeaders.getRawHeaders("Content-Type")[ 0] # type: ignore[index] != "application/json"): logger.warning( "Peer %s made push connection with non-JSON content (type: %s)", peer.servername, # Type safety: the hasHeader call returned True, so getRawHeaders() # returns a nonempty list. request.requestHeaders.getRawHeaders("Content-Type") [0], # type: ignore[index] ) raise MatrixRestError(400, "M_NOT_JSON", "This endpoint expects JSON") try: # json.loads doesn't allow bytes in Python 3.5 inJson = json_decoder.decode( request.content.read().decode("UTF-8")) except ValueError: logger.warning("Peer %s made push connection with malformed JSON", peer.servername) raise MatrixRestError(400, "M_BAD_JSON", "Malformed JSON") if "sgAssocs" not in inJson: logger.warning( "Peer %s made push connection with no 'sgAssocs' key in JSON", peer.servername, ) raise MatrixRestError(400, "M_BAD_JSON", 'No "sgAssocs" key in JSON') failedIds: List[int] = [] globalAssocsStore = GlobalAssociationStore(self.sydent) # Ensure items are pulled out of the dictionary in order of origin_id. sg_assocs_raw: SignedAssociations = inJson.get("sgAssocs", {}) sg_assocs = sorted(sg_assocs_raw.items(), key=lambda k: int(k[0])) for originId, sgAssoc in sg_assocs: try: peer.verifySignedAssociation(sgAssoc) logger.debug( "Signed association from %s with origin ID %s verified", peer.servername, originId, ) # Don't bother adding if one has already failed: we add all of them or none so # we're only going to roll back the transaction anyway (but we continue to try # & verify the rest so we can give a complete list of the ones that don't # verify) if len(failedIds) > 0: continue assocObj = threePidAssocFromDict(sgAssoc) # ensure we are casefolding email addresses before hashing/storing assocObj.address = normalise_address(assocObj.address, assocObj.medium) if assocObj.mxid is not None: # Calculate the lookup hash with our own pepper for this association pepper = self.hashing_store.get_lookup_pepper() assert pepper is not None str_to_hash = " ".join( [assocObj.address, assocObj.medium, pepper], ) assocObj.lookup_hash = sha256_and_url_safe_base64( str_to_hash) # Add this association globalAssocsStore.addAssociation( assocObj, json.dumps(sgAssoc), peer.servername, originId, commit=False, ) else: logger.info( "Incoming deletion: removing associations for %s / %s", assocObj.medium, assocObj.address, ) globalAssocsStore.removeAssociation( assocObj.medium, assocObj.address) logger.info("Stored association origin ID %s from %s", originId, peer.servername) except Exception: failedIds.append(originId) logger.warning( "Failed to verify signed association from %s with origin ID %s", peer.servername, originId, ) twisted.python.log.err() if len(failedIds) > 0: self.sydent.db.rollback() request.setResponseCode(400) return { "errcode": "M_VERIFICATION_FAILED", "error": "Verification failed for one or more associations", "failed_ids": failedIds, } else: self.sydent.db.commit() return {"success": True}
def render_POST(self, request): peerCert = request.transport.getPeerCertificate() peerCertCn = peerCert.get_subject().commonName peerStore = PeerStore(self.sydent) peer = peerStore.getPeerByName(peerCertCn) if not peer: logger.warn("Got connection from %s but no peer found by that name", peerCertCn) request.setResponseCode(403) return {'errcode': 'M_UNKNOWN_PEER', 'error': 'This peer is not known to this server'} logger.info("Push connection made from peer %s", peer.servername) if not request.requestHeaders.hasHeader('Content-Type') or \ request.requestHeaders.getRawHeaders('Content-Type')[0] != 'application/json': logger.warn("Peer %s made push connection with non-JSON content (type: %s)", peer.servername, request.requestHeaders.getRawHeaders('Content-Type')[0]) return {'errcode': 'M_NOT_JSON', 'error': 'This endpoint expects JSON'} try: inJson = json.load(request.content) except ValueError: logger.warn("Peer %s made push connection with malformed JSON", peer.servername) return {'errcode': 'M_BAD_JSON', 'error': 'Malformed JSON'} if 'sgAssocs' not in inJson: logger.warn("Peer %s made push connection with no 'sgAssocs' key in JSON", peer.servername) return {'errcode': 'M_BAD_JSON', 'error': 'No "sgAssocs" key in JSON'} failedIds = [] globalAssocsStore = GlobalAssociationStore(self.sydent) for originId,sgAssoc in inJson['sgAssocs'].items(): try: peer.verifySignedAssociation(sgAssoc) logger.debug("Signed association from %s with origin ID %s verified", peer.servername, originId) # Don't bother adding if one has already failed: we add all of them or none so we're only going to # roll back the transaction anyway (but we continue to try & verify the rest so we can give a # complete list of the ones that don't verify) if len(failedIds) > 0: continue assocObj = threePidAssocFromDict(sgAssoc) if assocObj.mxid is not None: globalAssocsStore.addAssociation(assocObj, json.dumps(sgAssoc), peer.servername, originId, commit=False) else: logger.info("Incoming deletion: removing associations for %s / %s", assocObj.medium, assocObj.address) globalAssocsStore.removeAssociation(assocObj.medium, assocObj.address) logger.info("Stored association origin ID %s from %s", originId, peer.servername) except: failedIds.append(originId) logger.warn("Failed to verify signed association from %s with origin ID %s", peer.servername, originId) twisted.python.log.err() if len(failedIds) > 0: self.sydent.db.rollback() request.setResponseCode(400) return {'errcode': 'M_VERIFICATION_FAILED', 'error': 'Verification failed for one or more associations', 'failed_ids':failedIds} else: self.sydent.db.commit() return {'success':True}