ProPeler
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
opt
/
cpanel-ccs
/
txdav
/
common
/
datastore
/
podding
/
Filename :
resource.py
back
Copy
## # Copyright (c) 2013-2017 Apple Inc. All rights reserved. # # 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. ## from txweb2 import responsecode from txweb2.dav.noneprops import NonePropertyStore from txweb2.dav.util import allDataFromStream from txweb2.http import Response, HTTPError, StatusResponse, JSONResponse from txweb2.http_headers import MimeType, MimeDisposition from txweb2.stream import ProducerStream from twisted.internet.defer import succeed, returnValue, inlineCallbacks from twisted.internet.protocol import Protocol from twistedcaldav.extensions import DAVResource, \ DAVResourceWithoutChildrenMixin from twistedcaldav.resource import ReadOnlyNoCopyResourceMixIn from twistedcaldav.scheduling_store.caldav.resource import \ deliverSchedulePrivilegeSet from txdav.xml import element as davxml import base64 import json class ConduitResource(ReadOnlyNoCopyResourceMixIn, DAVResourceWithoutChildrenMixin, DAVResource): """ Podding cross-pod RPC conduit resource. Extends L{DAVResource} to provide cross-pod RPC functionality. """ def __init__(self, parent, store): """ @param parent: the parent resource of this one. """ assert parent is not None DAVResource.__init__(self, principalCollections=parent.principalCollections()) self.parent = parent self.store = store def deadProperties(self): if not hasattr(self, "_dead_properties"): self._dead_properties = NonePropertyStore(self) return self._dead_properties def etag(self): return succeed(None) def checkPreconditions(self, request): return None def resourceType(self): return davxml.ResourceType.ischeduleinbox def contentType(self): return MimeType.fromString("text/html; charset=utf-8") def isCollection(self): return False def isCalendarCollection(self): return False def isPseudoCalendarCollection(self): return False @inlineCallbacks def principalForCalendarUserAddress(self, address): for principalCollection in self.principalCollections(): principal = yield principalCollection.principalForCalendarUserAddress(address) if principal is not None: returnValue(principal) returnValue(None) def render(self, request): output = """<html> <head> <title>Podding Conduit Resource</title> </head> <body> <h1>Podding Conduit Resource.</h1> </body </html>""" response = Response(200, {}, output) response.headers.setHeader("content-type", MimeType("text", "html")) return response @inlineCallbacks 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) ## # ACL ## def supportedPrivileges(self, request): return succeed(deliverSchedulePrivilegeSet) def defaultAccessControlList(self): privs = ( davxml.Privilege(davxml.Read()), ) return succeed( davxml.ACL( # DAV:Read for all principals (includes anonymous) davxml.ACE( davxml.Principal(davxml.All()), davxml.Grant(*privs), davxml.Protected(), ), ) )