def createDocumentRoot(self): docroot = self.mktemp() os.mkdir(docroot) userResource = TestDAVPrincipalResource("/principals/users/user01") userResource.writeDeadProperty(TwistedPasswordProperty("user01")) principalCollection = TestPrincipalsCollection( "/principals/", children={"users": TestPrincipalsCollection( "/principals/users/", children={"user01": userResource})}) rootResource = self.resource_class( docroot, principalCollections=(principalCollection,)) portal = Portal(DavRealm()) portal.registerChecker(TwistedPropertyChecker()) credentialFactories = (basic.BasicCredentialFactory(""),) loginInterfaces = (IPrincipal,) self.site = Site(AuthenticationWrapper( rootResource, portal, credentialFactories, credentialFactories, loginInterfaces )) rootResource.setAccessControlList(self.grant(element.All())) for name, acl in ( ("none" , self.grant()), ("read" , self.grant(element.Read())), ("read-write" , self.grant(element.Read(), element.Write())), ("unlock" , self.grant(element.Unlock())), ("all" , self.grant(element.All())), ): filename = os.path.join(docroot, name) if not os.path.isfile(filename): file(filename, "w").close() resource = self.resource_class(filename) resource.setAccessControlList(acl) for name, acl in ( ("nobind" , self.grant()), ("bind" , self.grant(element.Bind())), ("unbind" , self.grant(element.Bind(), element.Unbind())), ): dirname = os.path.join(docroot, name) if not os.path.isdir(dirname): os.mkdir(dirname) resource = self.resource_class(dirname) resource.setAccessControlList(acl) return docroot
def work(): dst_path = os.path.join(self.docroot, "copy_dst") dst_uri = "/" + os.path.basename(dst_path) for src, status in ( ("nobind", responsecode.FORBIDDEN), ("bind", responsecode.FORBIDDEN), ("unbind", responsecode.CREATED), ): src_path = os.path.join(self.docroot, "src_" + src) src_uri = "/" + os.path.basename(src_path) if not os.path.isdir(src_path): os.mkdir(src_path) src_resource = self.resource_class(src_path) src_resource.setAccessControlList({ "nobind": self.grant(), "bind" : self.grant(element.Bind()), "unbind": self.grant(element.Bind(), element.Unbind()) }[src]) for name, acl in ( ("none" , self.grant()), ("read" , self.grant(element.Read())), ("read-write" , self.grant(element.Read(), element.Write())), ("unlock" , self.grant(element.Unlock())), ("all" , self.grant(element.All())), ): filename = os.path.join(src_path, name) if not os.path.isfile(filename): file(filename, "w").close() self.resource_class(filename).setAccessControlList(acl) for method in ("COPY", "MOVE"): for name, code in ( ("none", {"COPY": responsecode.FORBIDDEN, "MOVE": status}[method]), ("read", {"COPY": responsecode.CREATED, "MOVE": status}[method]), ("read-write" , {"COPY": responsecode.CREATED, "MOVE": status}[method]), ("unlock", {"COPY": responsecode.FORBIDDEN, "MOVE": status}[method]), ("all", {"COPY": responsecode.CREATED, "MOVE": status}[method]), ): path = os.path.join(src_path, name) uri = src_uri + "/" + name request = SimpleRequest(self.site, method, uri) request.headers.setHeader("destination", dst_uri) _add_auth_header(request) def test(response, code=code, path=path): if os.path.isfile(dst_path): os.remove(dst_path) if response.code != code: return self.oops(request, response, code, method, name) yield (request, test)
def test_Privilege_isAggregateOf(self): """ Privilege.isAggregateOf() """ for a, b in ( (davxml.All(), davxml.Write()), (davxml.All(), davxml.ReadACL()), (davxml.Write(), davxml.WriteProperties()), (davxml.Write(), davxml.WriteContent()), (davxml.Write(), davxml.Bind()), (davxml.Write(), davxml.Unbind()), ): pa = davxml.Privilege(a) pb = davxml.Privilege(b) self.failUnless(pa.isAggregateOf(pb, davPrivilegeSet), "%s contains %s" % (a.sname(), b.sname())) self.failIf(pb.isAggregateOf(pa, davPrivilegeSet), "%s does not contain %s" % (b.sname(), a.sname())) for a, b in ( (davxml.Unlock(), davxml.Write()), (davxml.Unlock(), davxml.WriteACL()), (davxml.ReadCurrentUserPrivilegeSet(), davxml.WriteProperties()), ): pa = davxml.Privilege(a) pb = davxml.Privilege(b) self.failIf(pb.isAggregateOf(pa, davPrivilegeSet), "%s does not contain %s" % (b.sname(), a.sname()))
def http_MKCOL(self, request): """ Respond to a MKCOL request. (RFC 2518, section 8.3) """ parent = waitForDeferred(request.locateResource(parentForURL(request.uri))) yield parent parent = parent.getResult() x = waitForDeferred(parent.authorize(request, (davxml.Bind(), ))) yield x x.getResult() if self.exists(): log.error("Attempt to create collection where file exists: %s" % (self, )) raise HTTPError(responsecode.NOT_ALLOWED) if not parent.isCollection(): log.error( "Attempt to create collection with non-collection parent: %s" % (self, )) raise HTTPError( StatusResponse(responsecode.CONFLICT, "Parent resource is not a collection.")) # # Read request body # x = waitForDeferred(noDataFromStream(request.stream)) yield x try: x.getResult() except ValueError, e: log.error("Error while handling MKCOL body: %s" % (e, )) raise HTTPError(responsecode.UNSUPPORTED_MEDIA_TYPE)
def http_PROPFIND(self, request): """ Respond to a PROPFIND request. (RFC 2518, section 8.1) """ if not self.exists(): # Return 403 if parent does not allow Bind parentURL = parentForURL(request.uri) parent = (yield request.locateResource(parentURL)) yield parent.authorize(request, (davxml.Bind(), )) log.error("Resource not found: %s" % (self, )) raise HTTPError(responsecode.NOT_FOUND) # # Check authentication and access controls # yield self.authorize(request, (davxml.Read(), )) # # Read request body # try: doc = (yield davXMLFromStream(request.stream)) except ValueError, e: log.error("Error while handling PROPFIND body: %s" % (e, )) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
def authorize(self, request): if self.exists(): d = self.authorize(request, (davxml.Read(), )) else: d = request.locateResource(parentForURL(request.uri)) d.addCallback(lambda parent: parent.authorize(request, (davxml.Bind(), ))) return d
def http_COPY(self, request): """ Respond to a COPY request. (RFC 2518, section 8.8) """ r = waitForDeferred(prepareForCopy(self, request)) yield r r = r.getResult() destination, destination_uri, depth = r # # Check authentication and access controls # x = waitForDeferred( self.authorize(request, (davxml.Read(), ), recurse=True)) yield x x.getResult() if destination.exists(): x = waitForDeferred( destination.authorize( request, (davxml.WriteContent(), davxml.WriteProperties()), recurse=True)) yield x x.getResult() else: destparent = waitForDeferred( request.locateResource(parentForURL(destination_uri))) yield destparent destparent = destparent.getResult() x = waitForDeferred(destparent.authorize(request, (davxml.Bind(), ))) yield x x.getResult() # May need to add a location header addLocation(request, destination_uri) # x = waitForDeferred(copy(self.fp, destination.fp, destination_uri, depth)) x = waitForDeferred( put_common.storeResource(request, source=self, source_uri=request.uri, destination=destination, destination_uri=destination_uri, deletesource=False, depth=depth)) yield x yield x.getResult()
def preconditions_PUT(self, request): # # Check authentication and access controls # if self.exists(): x = waitForDeferred(self.authorize(request, (davxml.WriteContent(), ))) yield x x.getResult() else: parent = waitForDeferred( request.locateResource(parentForURL(request.uri))) yield parent parent = parent.getResult() if not parent.exists(): raise HTTPError( StatusResponse(responsecode.CONFLICT, "cannot PUT to non-existent parent")) x = waitForDeferred(parent.authorize(request, (davxml.Bind(), ))) yield x x.getResult() # # HTTP/1.1 (RFC 2068, section 9.6) requires that we respond with a Not # Implemented error if we get a Content-* header which we don't # recognize and handle properly. # for header, _ignore_value in request.headers.getAllRawHeaders(): if header.startswith("Content-") and header not in ( # "Content-Base", # Doesn't make sense in PUT? # "Content-Encoding", # Requires that we decode it? "Content-Language", "Content-Length", # "Content-Location", # Doesn't make sense in PUT? "Content-MD5", # "Content-Range", # FIXME: Need to implement this "Content-Type", ): log.error( "Client sent unrecognized content header in PUT request: %s" % (header, )) raise HTTPError( StatusResponse( responsecode.NOT_IMPLEMENTED, "Unrecognized content header %r in request." % (header, )))
def http_MKCALENDAR(self, request): """ Respond to a MKCALENDAR request. (CalDAV-access-09, section 5.3.1) """ # # Check authentication and access controls # parent = (yield request.locateResource(parentForURL(request.uri))) yield parent.authorize(request, (davxml.Bind(), )) if self.exists(): log.error("Attempt to create collection where resource exists: %s" % (self, )) raise HTTPError( ErrorResponse( responsecode.FORBIDDEN, (davxml.dav_namespace, "resource-must-be-null"), "Resource already exists", )) if not parent.isCollection(): log.error( "Attempt to create collection with non-collection parent: %s" % (self, )) raise HTTPError( ErrorResponse( responsecode.CONFLICT, (caldavxml.caldav_namespace, "calendar-collection-location-ok"), "Cannot create calendar inside another calendar", )) # # Read request body # try: doc = (yield davXMLFromStream(request.stream)) yield self.createCalendar(request) except ValueError, e: log.error("Error while handling MKCALENDAR: %s" % (e, )) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
def http_MKCOL(self, request): # # Check authentication and access controls # parent = (yield request.locateResource(parentForURL(request.uri))) yield parent.authorize(request, (davxml.Bind(), )) if self.exists(): log.error("Attempt to create collection where resource exists: {s!r}", s=self) raise HTTPError( ErrorResponse( responsecode.FORBIDDEN, (davxml.dav_namespace, "resource-must-be-null"), "Resource already exists", )) if not parent.isCollection(): log.error( "Attempt to create collection with non-collection parent: {s!r}", s=self) raise HTTPError( ErrorResponse( responsecode.CONFLICT, (davxml.dav_namespace, "collection-location-ok"), "Cannot create calendar inside another calendar", )) # # Don't allow DAV collections in a calendar or address book collection # if config.EnableCalDAV: parent = (yield self._checkParents(request, isPseudoCalendarCollectionResource)) if parent is not None: raise HTTPError( StatusResponse( responsecode.FORBIDDEN, "Cannot create collection within calendar collection %s" % (parent, ))) if config.EnableCardDAV: parent = (yield self._checkParents(request, isAddressBookCollectionResource)) if parent is not None: raise HTTPError( StatusResponse( responsecode.FORBIDDEN, "Cannot create collection within address book collection %s" % (parent, ))) # # Read request body # try: doc = (yield davXMLFromStream(request.stream)) except ValueError, e: log.error("Error while handling MKCOL: {ex}", ex=e) # TODO: txweb2.dav 'MKCOL' tests demand this particular response # code, but should we really be looking at the XML content or the # content-type header? It seems to me like this ought to be considered # a BAD_REQUEST if it claims to be XML but isn't, but an # UNSUPPORTED_MEDIA_TYPE if it claims to be something else. -glyph raise HTTPError( StatusResponse(responsecode.UNSUPPORTED_MEDIA_TYPE, str(e)))
def http_MOVE(self, request): """ Respond to a MOVE request. (RFC 2518, section 8.9) """ r = waitForDeferred(prepareForCopy(self, request)) yield r r = r.getResult() destination, destination_uri, depth = r # # Check authentication and access controls # parentURL = parentForURL(request.uri) parent = waitForDeferred(request.locateResource(parentURL)) yield parent parent = parent.getResult() x = waitForDeferred(parent.authorize(request, (davxml.Unbind(), ))) yield x x.getResult() if destination.exists(): x = waitForDeferred( destination.authorize(request, (davxml.Bind(), davxml.Unbind()), recurse=True)) yield x x.getResult() else: destparentURL = parentForURL(destination_uri) destparent = waitForDeferred(request.locateResource(destparentURL)) yield destparent destparent = destparent.getResult() x = waitForDeferred(destparent.authorize(request, (davxml.Bind(), ))) yield x x.getResult() # May need to add a location header addLocation(request, destination_uri) # # RFC 2518, section 8.9 says that we must act as if the Depth header is set # to infinity, and that the client must omit the Depth header or set it to # infinity. # # This seems somewhat at odds with the notion that a bad request should be # rejected outright; if the client sends a bad depth header, the client is # broken, and section 8 suggests that a bad request should be rejected... # # Let's play it safe for now and ignore broken clients. # if self.isCollection() and depth != "infinity": msg = "Client sent illegal depth header value for MOVE: %s" % (depth, ) log.error(msg) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg)) # Lets optimise a move within the same directory to a new resource as a simple move # rather than using the full transaction based storeResource api. This allows simple # "rename" operations to work quickly. if (not destination.exists()) and destparent == parent: x = waitForDeferred( move(self.fp, request.uri, destination.fp, destination_uri, depth)) else: x = waitForDeferred( put_common.storeResource(request, source=self, source_uri=request.uri, destination=destination, destination_uri=destination_uri, deletesource=True, depth=depth)) yield x yield x.getResult()