def http_POST(self, request): """ POST method with JSON body is used for control. """ # # Check authentication and access controls # yield self.authorize(request, (davxml.Read(),)) contentType = request.headers.getHeader("content-type") # Check content first if "{}/{}".format(contentType.mediaType, contentType.mediaSubtype) != "application/json": self.log.error("MIME type {mime} not allowed in request", mime=contentType) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "MIME type {} not allowed in request".format(contentType))) body = (yield allDataFromStream(request.stream)) try: j = json.loads(body) except (ValueError, TypeError) as e: self.log.error("Invalid JSON data in request: {ex}\n{body}", ex=e, body=body) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Invalid JSON data in request: {}\n{}".format(e, body))) try: action = j["action"] except KeyError: self._error("error", "No 'action' member in root JSON object.") method = "action_{}".format(action) if not hasattr(self, method): self._error("error", "The action '{}' is not supported.".format(action)) result = yield getattr(self, method)(j) returnValue(result)
def submitRequest(self, request, *args, **kwargs): """ Select an available client and perform the given request on it. @param command: A C{str} representing an attribute of L{MemCacheProtocol}. @parma args: Any positional arguments that should be passed to C{command}. @param kwargs: Any keyword arguments that should be passed to C{command}. @return: A L{Deferred} that fires with the result of the given command. """ # Since we may need to replay the request we have to read the request.stream # into memory and reset request.stream to use a MemoryStream each time we repeat # the request data = (yield allDataFromStream(request.stream)) # Try this maxRetries times for ctr in xrange(self.maxRetries + 1): try: request.stream = MemoryStream(data if data is not None else "") request.stream.doStartReading = None response = (yield self._submitRequest(request, args, kwargs)) except (ConnectionLost, ConnectionDone, ConnectError), e: self.log.error("HTTP pooled client connection error (attempt: %d) - retrying: %s" % (ctr + 1, e,)) continue # TODO: find the proper cause of these assertions and fix except (AssertionError,), e: self.log.error("HTTP pooled client connection assertion error (attempt: %d) - retrying: %s" % (ctr + 1, e,)) continue
def test_receive_fake_conduit(self): """ Cross-pod request works when conduit does support the action. """ store = self.storeUnderTest() self.patch(store, "conduit", self.FakeConduit(store)) request = SimpleRequest( self.site, "POST", "/conduit", headers=http_headers.Headers(rawHeaders={ "Content-Type": ("application/json",), self.thisServer.secretHeader()[0]: self.thisServer.secretHeader()[1], }), content=""" { "action":"fake", "echo":"bravo" } """.replace("\n", "\r\n") ) response = (yield self.send(request)) self.assertEqual(response.code, responsecode.OK) data = (yield allDataFromStream(response.stream)) j = json.loads(data) self.assertTrue("result" in j) self.assertEqual(j["result"], "ok") self.assertTrue("back2u" in j) self.assertEqual(j["back2u"], "bravo") self.assertTrue("more" in j) self.assertEqual(j["more"], "bits")
def logResponse(self, response): """ Log an HTTP request. """ iostr = StringIO() iostr.write(">>>> Response start\n\n") code_message = responsecode.RESPONSES.get(response.code, "Unknown Status") iostr.write("HTTP/1.1 {:d} {}\n".format(response.code, code_message)) for name, valuelist in response.headers.getAllRawHeaders(): for value in valuelist: # Do not log authorization details if name not in ("WWW-Authenticate",): iostr.write("{}: {}\n".format(name, value)) else: iostr.write("{}: xxxxxxxxx\n".format(name)) iostr.write("\n") # We need to play a trick with the response stream to ensure we don't mess it up. So we # read it, store the value in a MemoryStream, and replace the response's stream with that, # so the data can be read again. data = (yield allDataFromStream(response.stream)) iostr.write(data) response.stream = MemoryStream(data if data is not None else "") response.stream.doStartReading = None iostr.write("\n\n>>>> Response end\n") returnValue(iostr.getvalue())
def doRequest(self, txn): # Generate an HTTP client request try: if "xpod" not in txn.logItems: txn.logItems["xpod"] = 0 txn.logItems["xpod"] += 1 response = (yield self._processRequest()) if accountingEnabledForCategory("xPod"): self.loggedResponse = yield self.logResponse(response) emitAccounting("xPod", "", self.loggedRequest + "\n" + self.loggedResponse, "POST") if response.code in (responsecode.OK,): data = (yield allDataFromStream(response.stream)) data = json.loads(data) else: raise ValueError("Incorrect cross-pod response status code: {}".format(response.code)) except Exception as e: # Request failed log.error("Could not do cross-pod request : {request} {ex}", request=self, ex=e) raise ValueError("Failed cross-pod request: {}".format(e)) returnValue(data)
def test_receive_fake_conduit(self): """ Cross-pod request works when conduit does support the action. """ store = self.storeUnderTest() self.patch(store, "conduit", self.FakeConduit(store)) request = SimpleRequest(self.site, "POST", "/conduit", headers=http_headers.Headers( rawHeaders={ "Content-Type": ("application/json", ), self.thisServer.secretHeader()[0]: self.thisServer.secretHeader()[1], }), content=""" { "action":"fake", "echo":"bravo" } """.replace("\n", "\r\n")) response = (yield self.send(request)) self.assertEqual(response.code, responsecode.OK) data = (yield allDataFromStream(response.stream)) j = json.loads(data) self.assertTrue("result" in j) self.assertEqual(j["result"], "ok") self.assertTrue("value" in j) self.assertEqual(j["value"], {"back2u": "bravo", "more": "bits"})
def test_receive_ping(self): """ Cross-pod request works with the "ping" action. """ request = SimpleRequest(self.site, "POST", "/conduit", headers=http_headers.Headers( rawHeaders={ "Content-Type": ("application/json", ), self.thisServer.secretHeader()[0]: self.thisServer.secretHeader()[1], }), content=""" { "action":"ping" } """.replace("\n", "\r\n")) response = (yield self.send(request)) self.assertEqual(response.code, responsecode.OK) data = (yield allDataFromStream(response.stream)) j = json.loads(data) self.assertTrue("result" in j) self.assertEqual(j["result"], "ok")
def _verify(hdrs, body, keys, result, sign_headers=("Originator", "Recipient", "Content-Type",), manipulate_request=None): for algorithm in ("rsa-sha1", "rsa-sha256",): # Create signature stream = MemoryStream(body) headers = Headers() for name, value in [hdr.split(":", 1) for hdr in hdrs.splitlines()]: headers.addRawHeader(name, value) request = DKIMRequest("POST", "/", headers, stream, "example.com", "dkim", self.private_keyfile, algorithm, sign_headers, True, True, True, 3600) yield request.sign() # Possibly munge the request after the signature is done if manipulate_request is not None: manipulate_request(request) # Verify signature TestPublicKeyLookup.PublicKeyLookup_Testing.keys = keys data = (yield allDataFromStream(request.stream)) verifier = DKIMVerifier(request.headers, data, key_lookup=(TestPublicKeyLookup.PublicKeyLookup_Testing,)) TestPublicKeyLookup.PublicKeyLookup_Testing.flushCache() try: yield verifier.verify() except Exception, e: if result: self.fail("DKIMVerifier:verify failed: %s" % (e,)) else: if not result: self.fail("DKIMVerifier:verify did not fail")
def test_receive_ping(self): """ Cross-pod request works with the "ping" action. """ request = SimpleRequest( self.site, "POST", "/conduit", headers=http_headers.Headers(rawHeaders={ "Content-Type": ("application/json",), self.thisServer.secretHeader()[0]: self.thisServer.secretHeader()[1], }), content=""" { "action":"ping" } """.replace("\n", "\r\n") ) response = (yield self.send(request)) self.assertEqual(response.code, responsecode.OK) data = (yield allDataFromStream(response.stream)) j = json.loads(data) self.assertTrue("result" in j) self.assertEqual(j["result"], "ok")
def _verify(hdrs, body, keys, result, sign_headers=("Originator", "Recipient", "Content-Type",), manipulate_request=None): for algorithm in ("rsa-sha1", "rsa-sha256",): # Create signature stream = MemoryStream(body) headers = Headers() for name, value in [hdr.split(":", 1) for hdr in hdrs.splitlines()]: headers.addRawHeader(name, value) request = DKIMRequest("POST", "/", headers, stream, "example.com", "dkim", self.private_keyfile, algorithm, sign_headers, True, True, True, 3600) yield request.sign() # Possibly munge the request after the signature is done if manipulate_request is not None: manipulate_request(request) # Verify signature TestPublicKeyLookup.PublicKeyLookup_Testing.keys = keys data = (yield allDataFromStream(request.stream)) verifier = DKIMVerifier(request.headers, data, key_lookup=(TestPublicKeyLookup.PublicKeyLookup_Testing,)) TestPublicKeyLookup.PublicKeyLookup_Testing({}).flushCache() try: yield verifier.verify() except Exception, e: if result: self.fail("DKIMVerifier:verify failed: %s" % (e,)) else: if not result: self.fail("DKIMVerifier:verify did not fail")
def logRequest(self, request): """ Log an HTTP request. """ iostr = StringIO() iostr.write(">>>> Request start\n\n") if hasattr(request, "clientproto"): protocol = "HTTP/{:d}.{:d}".format(request.clientproto[0], request.clientproto[1]) else: protocol = "HTTP/1.1" iostr.write("{} {} {}\n".format(request.method, request.uri, protocol)) for name, valuelist in request.headers.getAllRawHeaders(): for value in valuelist: # Do not log authorization details if name not in ("Authorization",): iostr.write("{}: {}\n".format(name, value)) else: iostr.write("{}: xxxxxxxxx\n".format(name)) iostr.write("\n") # We need to play a trick with the request stream as we can only read it once. So we # read it, store the value in a MemoryStream, and replace the request's stream with that, # so the data can be read again. Note if we are sending an attachment, we won't log # the attachment data as we do not want to read it all into memory. if self.stream is None: data = (yield allDataFromStream(request.stream)) iostr.write(data) request.stream = MemoryStream(data if data is not None else "") request.stream.doStartReading = None else: iostr.write("<<Stream Type: {}>>\n".format(self.streamType)) iostr.write("\n\n>>>> Request end\n") returnValue(iostr.getvalue())
def doRequest(self, txn): # Generate an HTTP client request try: if "xpod" not in txn.logItems: txn.logItems["xpod"] = 0 txn.logItems["xpod"] += 1 response = (yield self._processRequest()) if accountingEnabledForCategory("xPod"): self.loggedResponse = yield self.logResponse(response) emitAccounting("xPod", "", self.loggedRequest + "\n" + self.loggedResponse, "POST") if response.code == responsecode.OK: if self.writeStream is None: data = (yield allDataFromStream(response.stream)) data = json.loads(data) else: yield readStream(response.stream, self.writeStream.write) content_type = response.headers.getHeader("content-type") if content_type is None: content_type = MimeType("application", "octet-stream") content_disposition = response.headers.getHeader("content-disposition") if content_disposition is None or "filename" not in content_disposition.params: filename = "" else: filename = content_disposition.params["filename"] self.writeStream.resetDetails(content_type, filename) yield self.writeStream.loseConnection() data = { "result": "ok", "content-type": content_type, "name": filename, } elif response.code == responsecode.BAD_REQUEST: data = (yield allDataFromStream(response.stream)) data = json.loads(data) else: raise ValueError("Incorrect cross-pod response status code: {}".format(response.code)) except Exception as e: # Request failed log.error("Could not do cross-pod request : {request} {ex}", request=self, ex=e) raise ValueError("Failed cross-pod request: {}".format(e)) returnValue(data)
def http_POST(self, request): """ The server-to-server POST method. """ # Need a transaction to work with txn = transactionFromRequest(request, self._newStore) # Log extended item if not hasattr(request, "extendedLogItems"): request.extendedLogItems = {} # This is a server-to-server scheduling operation. scheduler = IScheduleScheduler(txn, None, logItems=request.extendedLogItems, podding=self._podding) # Check content first contentType = request.headers.getHeader("content-type") format = self.determineType(contentType) if format is None: msg = "MIME type {} not allowed in iSchedule request".format( contentType, ) self.log.error(msg) raise HTTPError( scheduler.errorResponse( responsecode.FORBIDDEN, (ischedule_namespace, "invalid-calendar-data-type"), msg, )) originator = self.loadOriginatorFromRequestHeaders(request) recipients = self.loadRecipientsFromRequestHeaders(request) body = (yield allDataFromStream(request.stream)) calendar = Component.fromString(body, format=format) # Do the POST processing treating this as a non-local schedule try: result = (yield scheduler.doSchedulingViaPOST(request.remoteAddr, request.headers, body, calendar, originator, recipients)) except Exception: ex = Failure() yield txn.abort() ex.raiseException() else: yield txn.commit() response = result.response(format=format) if not self._podding: response.headers.addRawHeader( ISCHEDULE_CAPABILITIES, str(config.Scheduling.iSchedule.SerialNumber)) returnValue(response)
def assertResponse(self, response, expected): self.assertNotEquals(response, None, "Got None instead of a response.") self.assertEquals(response.code, expected[0]) self.assertEquals(set(response.headers.getAllRawHeaders()), set(expected[1].getAllRawHeaders())) d = allDataFromStream(response.stream) d.addCallback(self.assertEquals, expected[2]) return d
def http_POST(self, request): """ The server-to-server POST method. """ # Check shared secret if not self.store.directoryService().serversDB.getThisServer().checkSharedSecret(request.headers): self.log.error("Invalid shared secret header in cross-pod request") raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Not authorized to make this request")) # Look for XPOD header xpod = request.headers.getRawHeaders("XPOD") contentType = request.headers.getHeader("content-type") if xpod is not None: # Attachments are sent in the request body with the JSON data in a header. We # decode the header and add the request.stream as an attribute of the JSON object. xpod = xpod[0] try: j = json.loads(base64.b64decode(xpod)) except (TypeError, ValueError) as e: self.log.error("Invalid JSON header in request: {ex}\n{xpod}", ex=e, xpod=xpod) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Invalid JSON header in request: {}\n{}".format(e, xpod))) j["stream"] = request.stream j["streamType"] = contentType else: # Check content first if "{}/{}".format(contentType.mediaType, contentType.mediaSubtype) != "application/json": self.log.error("MIME type {mime} not allowed in request", mime=contentType) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "MIME type {} not allowed in request".format(contentType))) body = (yield allDataFromStream(request.stream)) try: j = json.loads(body) except ValueError as e: self.log.error("Invalid JSON data in request: {ex}\n{body}", ex=e, body=body) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Invalid JSON data in request: {}\n{}".format(e, body))) # Log extended item if not hasattr(request, "extendedLogItems"): request.extendedLogItems = {} request.extendedLogItems["xpod"] = j["action"] if "action" in j else "unknown" # Get the conduit to process the data try: result = yield self.store.conduit.processRequest(j) except FailedCrossPodRequestError as e: raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e))) except Exception as e: raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, str(e))) response = JSONResponse(responsecode.OK, result) returnValue(response)
def bodyHash(self): """ Generate the hash of the request body data. """ # We need to play a trick with the request stream as we can only read it once. So we # read it, store the value in a MemoryStream, and replace the request's stream with that, # so the data can be read again. data = (yield allDataFromStream(self.stream)) self.stream = MemoryStream(data if data is not None else "") self.stream.doStartReading = None returnValue(base64.b64encode(self.hash_method(DKIMUtils.canonicalizeBody(data)).digest()))
def xmlRequestHandler(self, request): # Need to read the data and get the root element first xmldata = (yield allDataFromStream(request.stream)) try: doc = element.WebDAVDocument.fromString(xmldata) except ValueError, e: self.log.error("Error parsing doc (%s) Doc:\n %s" % (str(e), xmldata,)) raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, (customxml.calendarserver_namespace, "valid-request"), "Invalid XML", ))
def xmlRequestHandler(self, request): # Need to read the data and get the root element first xmldata = (yield allDataFromStream(request.stream)) try: doc = element.WebDAVDocument.fromString(xmldata) except ValueError, e: self.log.error("Error parsing doc ({ex}) Doc:\n {x}", ex=str(e), x=xmldata) raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, (customxml.calendarserver_namespace, "valid-request"), "Invalid XML", ))
def listChildrenViaPropfind(): authPrincipal = yield self.actualRoot.findPrincipalForAuthID("user01") request = SimpleStoreRequest(self, "PROPFIND", "/calendars/__uids__/user01/", authPrincipal=authPrincipal) request.headers.setHeader("depth", "1") response = yield self.send(request) response = IResponse(response) data = yield allDataFromStream(response.stream) tree = XML(data) seq = [e.text for e in tree.findall("{DAV:}response/{DAV:}href")] shortest = min(seq, key=len) seq.remove(shortest) filtered = [elem[len(shortest):].rstrip("/") for elem in seq] returnValue(filtered)
def _doPOSTSharerAccept(self, body, resultcode=responsecode.OK, sharer="user02"): authPrincipal = yield self.actualRoot.findPrincipalForAuthID(sharer) request = SimpleStoreRequest(self, "POST", "/calendars/__uids__/{}/".format(sharer), content=body, authPrincipal=authPrincipal) request.headers.setHeader("content-type", MimeType("text", "xml")) response = yield self.send(request) response = IResponse(response) self.assertEqual(response.code, resultcode) if response.stream: xmldata = yield allDataFromStream(response.stream) doc = WebDAVDocument.fromString(xmldata) returnValue(doc) else: returnValue(None)
def _doPOST(self, body, resultcode=responsecode.OK): authPrincipal = yield self.actualRoot.findPrincipalForAuthID("user01") request = SimpleStoreRequest(self, "POST", "/calendars/__uids__/user01/calendar/", content=body, authPrincipal=authPrincipal) request.headers.setHeader("content-type", MimeType("text", "xml")) response = yield self.send(request) response = IResponse(response) self.assertEqual(response.code, resultcode) # Reload resource self.resource = yield self._getResource() if response.stream: data = yield allDataFromStream(response.stream) returnValue(data) else: returnValue(None)
def submitRequest(self, request, *args, **kwargs): """ Select an available client and perform the given request on it. @param command: A C{str} representing an attribute of L{MemCacheProtocol}. @parma args: Any positional arguments that should be passed to C{command}. @param kwargs: Any keyword arguments that should be passed to C{command}. @return: A L{Deferred} that fires with the result of the given command. """ # Since we may need to replay the request we have to read the request.stream # into memory and reset request.stream to use a MemoryStream each time we repeat # the request data = (yield allDataFromStream(request.stream)) # Try this maxRetries times for ctr in xrange(self.maxRetries + 1): try: request.stream = MemoryStream(data if data is not None else "") request.stream.doStartReading = None response = (yield self._submitRequest(request, args, kwargs)) except (ConnectionLost, ConnectionDone, ConnectError), e: self.log.error( "HTTP pooled client connection error (attempt: %d) - retrying: %s" % ( ctr + 1, e, )) continue # TODO: find the proper cause of these assertions and fix except (AssertionError, ), e: self.log.error( "HTTP pooled client connection assertion error (attempt: %d) - retrying: %s" % ( ctr + 1, e, )) continue
def listChildrenViaPropfind(): authPrincipal = yield self.actualRoot.findPrincipalForAuthID( "user01") request = SimpleStoreRequest(self, "PROPFIND", "/calendars/__uids__/user01/", authPrincipal=authPrincipal) request.headers.setHeader("depth", "1") response = yield self.send(request) response = IResponse(response) data = yield allDataFromStream(response.stream) tree = XML(data) seq = [e.text for e in tree.findall("{DAV:}response/{DAV:}href")] shortest = min(seq, key=len) seq.remove(shortest) filtered = [elem[len(shortest):].rstrip("/") for elem in seq] returnValue(filtered)
def http_POST(self, request): """ The server-to-server POST method. """ # Need a transaction to work with txn = transactionFromRequest(request, self._newStore) # This is a server-to-server scheduling operation. scheduler = IScheduleScheduler(txn, None, podding=self._podding) # Check content first contentType = request.headers.getHeader("content-type") format = self.determineType(contentType) if format is None: msg = "MIME type {} not allowed in iSchedule request".format(contentType,) self.log.error(msg) raise HTTPError(scheduler.errorResponse( responsecode.FORBIDDEN, (ischedule_namespace, "invalid-calendar-data-type"), msg, )) originator = self.loadOriginatorFromRequestHeaders(request) recipients = self.loadRecipientsFromRequestHeaders(request) body = (yield allDataFromStream(request.stream)) calendar = Component.fromString(body, format=format) # Do the POST processing treating this as a non-local schedule try: result = (yield scheduler.doSchedulingViaPOST(request.remoteAddr, request.headers, body, calendar, originator, recipients)) except Exception: ex = Failure() yield txn.abort() ex.raiseException() else: yield txn.commit() response = result.response(format=format) if not self._podding: response.headers.addRawHeader(ISCHEDULE_CAPABILITIES, str(config.Scheduling.iSchedule.SerialNumber)) returnValue(response)
def calendar_query(self, query, got_xml, expected_code=responsecode.MULTI_STATUS): principal = yield self.actualRoot.findPrincipalForAuthID("wsanchez") request = SimpleStoreRequest(self, "REPORT", "/calendars/users/wsanchez/calendar/", authPrincipal=principal) request.stream = MemoryStream(query.toxml()) response = yield self.send(request) response = IResponse(response) if response.code != expected_code: self.fail("REPORT failed: %s" % (response.code,)) if got_xml is not None: returnValue( (yield davXMLFromStream(response.stream).addCallback(got_xml)) ) else: returnValue( (yield allDataFromStream(response.stream)) )
def _requestKey(self, request): """ Get a key for this request. This depends on the method, Depth: header, authn user principal, request uri and a hash of the request body (the body being normalized for property order). """ requestBody = (yield allDataFromStream(request.stream)) if requestBody is not None: # Give it back to the request so it can be read again request.stream = MemoryStream(requestBody) request.stream.doStartReading = None # Normalize the property order by doing a "dumb" sort on lines requestLines = requestBody.splitlines() requestLines.sort() requestBody = "\n".join(requestLines) request.cacheKey = (request.method, self._principalURI(request.authnUser), request.uri, request.headers.getHeader('depth'), hash(requestBody)) returnValue(request.cacheKey)
def calendar_query(self, query, got_xml, expected_code=responsecode.MULTI_STATUS): principal = yield self.actualRoot.findPrincipalForAuthID("wsanchez") request = SimpleStoreRequest(self, "REPORT", "/calendars/users/wsanchez/calendar/", authPrincipal=principal) request.stream = MemoryStream(query.toxml()) response = yield self.send(request) response = IResponse(response) if response.code != expected_code: self.fail("REPORT failed: %s" % (response.code, )) if got_xml is not None: returnValue( (yield davXMLFromStream(response.stream).addCallback(got_xml))) else: returnValue((yield allDataFromStream(response.stream)))
def logRequest(self, request): """ Log an HTTP request. """ iostr = StringIO() iostr.write(">>>> Request start\n\n") if hasattr(request, "clientproto"): protocol = "HTTP/{:d}.{:d}".format(request.clientproto[0], request.clientproto[1]) else: protocol = "HTTP/1.1" iostr.write("{} {} {}\n".format(request.method, request.uri, protocol)) for name, valuelist in request.headers.getAllRawHeaders(): for value in valuelist: # Do not log authorization details if name not in ("Authorization", ): iostr.write("{}: {}\n".format(name, value)) else: iostr.write("{}: xxxxxxxxx\n".format(name)) iostr.write("\n") # We need to play a trick with the request stream as we can only read it once. So we # read it, store the value in a MemoryStream, and replace the request's stream with that, # so the data can be read again. Note if we are sending an attachment, we won't log # the attachment data as we do not want to read it all into memory. if self.stream is None: data = (yield allDataFromStream(request.stream)) iostr.write(data) request.stream = MemoryStream(data if data is not None else "") request.stream.doStartReading = None else: iostr.write("<<Stream Type: {}>>\n".format(self.streamType)) iostr.write("\n\n>>>> Request end\n") returnValue(iostr.getvalue())
def checkResult(response): self.assertEqual(response.code, resultcode) if response.stream is None: return None return allDataFromStream(response.stream)
def http_POST(self, request): """ The server-to-server POST method. """ # Check shared secret if not self.store.directoryService().serversDB().getThisServer().checkSharedSecret(request.headers): self.log.error("Invalid shared secret header in cross-pod request") raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Not authorized to make this request")) # Look for XPOD header xpod = request.headers.getRawHeaders("XPOD") contentType = request.headers.getHeader("content-type") if xpod is not None: # Attachments are sent in the request body with the JSON data in a header. We # decode the header and add the request.stream as an attribute of the JSON object. xpod = xpod[0] try: j = json.loads(base64.b64decode(xpod)) except (TypeError, ValueError) as e: self.log.error("Invalid JSON header in request: {ex}\n{xpod}", ex=e, xpod=xpod) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Invalid JSON header in request: {}\n{}".format(e, xpod))) j["stream"] = request.stream j["streamType"] = contentType else: # Check content first if "{}/{}".format(contentType.mediaType, contentType.mediaSubtype) != "application/json": self.log.error("MIME type {mime} not allowed in request", mime=contentType) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "MIME type {} not allowed in request".format(contentType))) body = (yield allDataFromStream(request.stream)) try: j = json.loads(body) except ValueError as e: self.log.error("Invalid JSON data in request: {ex}\n{body}", ex=e, body=body) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Invalid JSON data in request: {}\n{}".format(e, body))) # Log extended item if not hasattr(request, "extendedLogItems"): request.extendedLogItems = {} request.extendedLogItems["xpod"] = j["action"] if "action" in j else "unknown" # Look for a streaming action which needs special handling if self.store.conduit.isStreamAction(j): # Get the conduit to process the data stream try: stream = ProducerStream() class StreamProtocol(Protocol): def connectionMade(self): stream.registerProducer(self.transport, False) def dataReceived(self, data): stream.write(data) def connectionLost(self, reason): stream.finish() result = yield self.store.conduit.processRequestStream(j, StreamProtocol()) try: ct, name = result except ValueError: code = responsecode.BAD_REQUEST else: headers = {"content-type": MimeType.fromString(ct)} headers["content-disposition"] = MimeDisposition("attachment", params={"filename": name}) returnValue(Response(responsecode.OK, headers, stream)) except Exception as e: # Send the exception over to the other side result = { "result": "exception", "class": ".".join((e.__class__.__module__, e.__class__.__name__,)), "details": str(e), } code = responsecode.BAD_REQUEST else: # Get the conduit to process the data try: result = yield self.store.conduit.processRequest(j) code = responsecode.OK if result["result"] == "ok" else responsecode.BAD_REQUEST except Exception as e: # Send the exception over to the other side result = { "result": "exception", "class": ".".join((e.__class__.__module__, e.__class__.__name__,)), "details": str(e), } code = responsecode.BAD_REQUEST response = JSONResponse(code, result) returnValue(response)
def http_POST(self, request): """ The server-to-server POST method. """ # Check shared secret if not self.store.directoryService().serversDB().getThisServer( ).checkSharedSecret(request.headers): self.log.error("Invalid shared secret header in cross-pod request") raise HTTPError( StatusResponse(responsecode.FORBIDDEN, "Not authorized to make this request")) # Look for XPOD header xpod = request.headers.getRawHeaders("XPOD") contentType = request.headers.getHeader("content-type") if xpod is not None: # Attachments are sent in the request body with the JSON data in a header. We # decode the header and add the request.stream as an attribute of the JSON object. xpod = xpod[0] try: j = json.loads(base64.b64decode(xpod)) except (TypeError, ValueError) as e: self.log.error("Invalid JSON header in request: {ex}\n{xpod}", ex=e, xpod=xpod) raise HTTPError( StatusResponse( responsecode.BAD_REQUEST, "Invalid JSON header in request: {}\n{}".format( e, xpod))) j["stream"] = request.stream j["streamType"] = contentType else: # Check content first if "{}/{}".format(contentType.mediaType, contentType.mediaSubtype) != "application/json": self.log.error("MIME type {mime} not allowed in request", mime=contentType) raise HTTPError( StatusResponse( responsecode.BAD_REQUEST, "MIME type {} not allowed in request".format( contentType))) body = (yield allDataFromStream(request.stream)) try: j = json.loads(body) except ValueError as e: self.log.error("Invalid JSON data in request: {ex}\n{body}", ex=e, body=body) raise HTTPError( StatusResponse( responsecode.BAD_REQUEST, "Invalid JSON data in request: {}\n{}".format(e, body))) # Log extended item if not hasattr(request, "extendedLogItems"): request.extendedLogItems = {} request.extendedLogItems[ "xpod"] = j["action"] if "action" in j else "unknown" # Look for a streaming action which needs special handling if self.store.conduit.isStreamAction(j): # Get the conduit to process the data stream try: stream = ProducerStream() class StreamProtocol(Protocol): def connectionMade(self): stream.registerProducer(self.transport, False) def dataReceived(self, data): stream.write(data) def connectionLost(self, reason): stream.finish() result = yield self.store.conduit.processRequestStream( j, StreamProtocol()) try: ct, name = result except ValueError: code = responsecode.BAD_REQUEST else: headers = {"content-type": MimeType.fromString(ct)} headers["content-disposition"] = MimeDisposition( "attachment", params={"filename": name}) returnValue(Response(responsecode.OK, headers, stream)) except Exception as e: # Send the exception over to the other side result = { "result": "exception", "class": ".".join(( e.__class__.__module__, e.__class__.__name__, )), "details": str(e), } code = responsecode.BAD_REQUEST else: # Get the conduit to process the data try: result = yield self.store.conduit.processRequest(j) code = responsecode.OK if result[ "result"] == "ok" else responsecode.BAD_REQUEST except Exception as e: # Send the exception over to the other side result = { "result": "exception", "class": ".".join(( e.__class__.__module__, e.__class__.__name__, )), "details": str(e), } code = responsecode.BAD_REQUEST response = JSONResponse(code, result) returnValue(response)
def _getResponseBody(self, key, response): d1 = allDataFromStream(response.stream) d1.addCallback(lambda responseBody: (key, responseBody)) return d1
def http_POST(self, request): """ The server-to-server POST method. """ # Check shared secret if not self.store.directoryService().serversDB.getThisServer( ).checkSharedSecret(request.headers): self.log.error("Invalid shared secret header in cross-pod request") raise HTTPError( StatusResponse(responsecode.FORBIDDEN, "Not authorized to make this request")) # Look for XPOD header xpod = request.headers.getRawHeaders("XPOD") contentType = request.headers.getHeader("content-type") if xpod is not None: # Attachments are sent in the request body with the JSON data in a header. We # decode the header and add the request.stream as an attribute of the JSON object. xpod = xpod[0] try: j = json.loads(base64.b64decode(xpod)) except (TypeError, ValueError) as e: self.log.error("Invalid JSON header in request: {ex}\n{xpod}", ex=e, xpod=xpod) raise HTTPError( StatusResponse( responsecode.BAD_REQUEST, "Invalid JSON header in request: {}\n{}".format( e, xpod))) j["stream"] = request.stream j["streamType"] = contentType else: # Check content first if "{}/{}".format(contentType.mediaType, contentType.mediaSubtype) != "application/json": self.log.error("MIME type {mime} not allowed in request", mime=contentType) raise HTTPError( StatusResponse( responsecode.BAD_REQUEST, "MIME type {} not allowed in request".format( contentType))) body = (yield allDataFromStream(request.stream)) try: j = json.loads(body) except ValueError as e: self.log.error("Invalid JSON data in request: {ex}\n{body}", ex=e, body=body) raise HTTPError( StatusResponse( responsecode.BAD_REQUEST, "Invalid JSON data in request: {}\n{}".format(e, body))) # Log extended item if not hasattr(request, "extendedLogItems"): request.extendedLogItems = {} request.extendedLogItems[ "xpod"] = j["action"] if "action" in j else "unknown" # Get the conduit to process the data try: result = yield self.store.conduit.processRequest(j) except FailedCrossPodRequestError as e: raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e))) except Exception as e: raise HTTPError( StatusResponse(responsecode.INTERNAL_SERVER_ERROR, str(e))) response = JSONResponse(responsecode.OK, result) returnValue(response)