123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101 |
- # -*- coding: utf-8 -*-
- # Copyright 2014 OpenMarket Ltd
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- import twisted.python.log
- from twisted.web.resource import Resource
- from sydent.http.servlets import jsonwrap
- from sydent.threepid import threePidAssocFromDict
- from sydent.db.peers import PeerStore
- from sydent.db.threepid_associations import GlobalAssociationStore
- import logging
- import json
- logger = logging.getLogger(__name__)
- class ReplicationPushServlet(Resource):
- def __init__(self, sydent):
- self.sydent = sydent
- @jsonwrap
- 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}
|