def defaultAccessControlList(self): privs = ( davxml.Privilege(davxml.Read()), davxml.Privilege(caldavxml.ScheduleDeliver()), ) if config.Scheduling.CalDAV.OldDraftCompatibility: privs += (davxml.Privilege(caldavxml.Schedule()),) aces = ( # DAV:Read, CalDAV:schedule-deliver for all principals (does not include anonymous) davxml.ACE( davxml.Principal(davxml.Authenticated()), davxml.Grant(*privs), davxml.Protected(), ), ) if config.FreeBusyURL.AnonymousAccess: aces += ( # DAV:Read, for unauthenticated principals davxml.ACE( davxml.Principal(davxml.Unauthenticated()), davxml.Grant( davxml.Privilege(davxml.Read()), ), davxml.Protected(), ), ) return succeed(davxml.ACL(*aces))
def _authReadOnlyPrivileges(self, resource, url, records=None): items = [] if records is None: records = yield self._allRecords() for ( _ignore_provisioningResource, _ignore_recordType, recordResource, _ignore_record ) in records: items.append(( davxml.HRef().fromString(recordResource.principalURL()), davxml.Read(), True )) items.append(( davxml.HRef().fromString(recordResource.principalURL()), davxml.Write(), False )) items.append(( davxml.Unauthenticated(), davxml.Read(), False )) items.append(( davxml.Unauthenticated(), davxml.Write(), False )) results = [] for principal, privilege, allowed in items: results.append((resource, url, principal, privilege, allowed)) returnValue(results)
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 http_GET(self, request): """ The timezone service GET method. """ yield self.authorize(request, (davxml.Read(),)) urlbits = map(urllib.unquote, request.path.strip("/").split("/")[1:]) child = urlbits[0] if len(urlbits) > 0 else "" if not child: if len(request.args) != 0: self.problemReport("invalid-action", "Invalid request-URI query parameters", responsecode.BAD_REQUEST) # Do normal GET behavior returnValue(self.render(request)) childResponder = { "capabilities" : self.childCapabilities, "zones" : self.childZones, "observances" : self.childObservances, }.get(child, None) if childResponder is None: self.problemReport("invalid-action", "Invalid action", responsecode.BAD_REQUEST) returnValue(childResponder(request, urlbits))
def http_REPORT(self, request): """ Respond to a REPORT request. (RFC 3253, section 3.6) """ if not self.exists(): log.error("File not found: %s" % (self, )) raise HTTPError(responsecode.NOT_FOUND) # # Check authentication and access controls # x = waitForDeferred(self.authorize(request, (davxml.Read(), ))) yield x x.getResult() # # Read request body # try: doc = waitForDeferred(davXMLFromStream(request.stream)) yield doc doc = doc.getResult() except ValueError, e: log.error("Error while handling REPORT body: %s" % (e, )) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
def defaultAccessControlList(self): aces = ( # DAV:read access for authenticated users. davxml.ACE( davxml.Principal(davxml.Authenticated()), davxml.Grant(davxml.Privilege(davxml.Read())), ), # Inheritable DAV:all access for the resource's associated principal. davxml.ACE( davxml.Principal(davxml.HRef(self.parent.principalURL())), davxml.Grant(davxml.Privilege(davxml.WriteProperties())), davxml.Protected(), ), ) # Add admins aces += tuple(( davxml.ACE( davxml.Principal(davxml.HRef(principal)), davxml.Grant(davxml.Privilege(davxml.All())), davxml.Protected(), ) for principal in config.AdminPrincipals )) return succeed(davxml.ACL(*aces))
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 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)))
class SimpleResource( CalDAVResource, ): allReadACL = davxml.ACL( # Read access for all users. davxml.ACE( davxml.Principal(davxml.All()), davxml.Grant(davxml.Privilege(davxml.Read())), davxml.Protected(), ), ) authReadACL = davxml.ACL( # Read access for authenticated users. davxml.ACE( davxml.Principal(davxml.Authenticated()), davxml.Grant(davxml.Privilege(davxml.Read())), davxml.Protected(), ), ) def __init__(self, principalCollections, isdir=False, defaultACL=authReadACL): """ Make sure it is a collection. """ CalDAVResource.__init__(self, principalCollections=principalCollections) self._isDir = isdir self.defaultACL = defaultACL def isCollection(self): return self._isDir 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 accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None): return succeed(self.defaultACL)
def checkSecurity(self, request): """ Locate the resource named by the given request's URI, then authorize it for the 'Read' permission. """ d = request.locateResource(request.uri) d.addCallback(lambda r: r.authorize(request, (davxml.Read(), ))) return d
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_GET(self, request): """ The server-info GET method. """ yield self.authorize(request, (davxml.Read(), )) returnValue(XMLResponse(responsecode.OK, config.ServerInfo))
def defaultAccessControlList(self): return succeed( davxml.ACL( davxml.ACE( davxml.Principal(davxml.Authenticated()), davxml.Grant(davxml.Privilege(davxml.Read()), ), davxml.Protected(), TwistedACLInheritable(), ), ))
def defaultAccessControlList(self): return succeed( davxml.ACL( # DAV:Read for all principals (includes anonymous) davxml.ACE( davxml.Principal(davxml.All()), davxml.Grant(davxml.Privilege(davxml.Read()), ), davxml.Protected(), ), ))
def _xmlHandleInviteReply(self, request, docroot): # Sharing must be enabled for this collection if not self.canShare(): raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, (customxml.calendarserver_namespace, "valid-request"), "Sharing not supported on this resource", )) yield self.authorize(request, (element.Read(), element.Write())) result = (yield self._handleInviteReply(request, docroot)) returnValue(result)
def defaultAccessControlList(self): privs = ( davxml.Privilege(davxml.Read()), davxml.Privilege(caldavxml.ScheduleDeliver()), ) return succeed( davxml.ACL( # DAV:Read, CalDAV:schedule-deliver for all principals (includes anonymous) davxml.ACE( davxml.Principal(davxml.All()), davxml.Grant(*privs), davxml.Protected(), ), ))
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 defaultAccessControlList(self): return succeed( davxml.ACL( # DAV:Read for authenticated principals davxml.ACE( davxml.Principal(davxml.Authenticated()), davxml.Grant(davxml.Privilege(davxml.Read()), ), davxml.Protected(), ), # DAV:Write for authenticated principals davxml.ACE( davxml.Principal(davxml.Authenticated()), davxml.Grant(davxml.Privilege(davxml.Write()), ), davxml.Protected(), ), ))
def defaultAccessControlList(self): if config.AnonymousDirectoryAddressBookAccess: # DAV:Read for all principals (includes anonymous) accessPrincipal = davxml.All() else: # DAV:Read for all authenticated principals (does not include anonymous) accessPrincipal = davxml.Authenticated() return succeed( davxml.ACL( davxml.ACE( davxml.Principal(accessPrincipal), davxml.Grant( davxml.Privilege(davxml.Read()), davxml.Privilege( davxml.ReadCurrentUserPrivilegeSet())), davxml.Protected(), TwistedACLInheritable(), ), ))
def http_POST(self, request): """ The timezone service POST method. """ # Check authentication and access controls def _gotResult(_): if not request.args: # Do normal GET behavior return self.render(request) method = request.args.get("method", ("", )) if len(method) != 1: raise HTTPError( ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-method"), "Invalid method query parameter", )) method = method[0] action = { "list": self.doPOSTList, "get": self.doPOSTGet, "expand": self.doPOSTExpand, }.get(method, None) if action is None: raise HTTPError( ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "supported-method"), "Unknown method query parameter", )) return action(request) d = self.authorize(request, (davxml.Read(), )) d.addCallback(_gotResult) return d
def report_urn_ietf_params_xml_ns_caldav_calendar_query(self, request, calendar_query): """ Generate a calendar-query REPORT. (CalDAV-access-09, section 7.6) """ # Verify root element if calendar_query.qname() != (caldav_namespace, "calendar-query"): raise ValueError("{CalDAV:}calendar-query expected as root element, not %s." % (calendar_query.sname(),)) if not self.isCollection(): parent = (yield self.locateParent(request, request.uri)) if not parent.isPseudoCalendarCollection(): log.error("calendar-query report is not allowed on a resource outside of a calendar collection %s" % (self,)) raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Must be calendar collection or calendar resource")) responses = [] xmlfilter = calendar_query.filter filter = Filter(xmlfilter) props = calendar_query.props assert props is not None # Get the original timezone provided in the query, if any, and validate it now query_timezone = None query_tz = calendar_query.timezone if query_tz is not None and not query_tz.valid(): msg = "CalDAV:timezone must contain one VTIMEZONE component only: %s" % (query_tz,) log.error(msg) raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data"), "Invalid calendar-data", )) if query_tz: filter.settimezone(query_tz) query_timezone = tuple(calendar_query.timezone.calendar().subcomponents())[0] if props.qname() == ("DAV:", "allprop"): propertiesForResource = report_common.allPropertiesForResource generate_calendar_data = False elif props.qname() == ("DAV:", "propname"): propertiesForResource = report_common.propertyNamesForResource generate_calendar_data = False elif props.qname() == ("DAV:", "prop"): propertiesForResource = report_common.propertyListForResource # Verify that any calendar-data element matches what we can handle result, message, generate_calendar_data = report_common.validPropertyListCalendarDataTypeVersion(props) if not result: log.error(message) raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, (caldav_namespace, "supported-calendar-data"), "Invalid calendar-data", )) else: raise AssertionError("We shouldn't be here") # Verify that the filter element is valid if (filter is None) or not filter.valid(): log.error("Invalid filter element: %r" % (xmlfilter,)) raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, (caldav_namespace, "valid-filter"), "Invalid filter element", )) matchcount = [0] max_number_of_results = [config.MaxQueryWithDataResults if generate_calendar_data else None, ] @inlineCallbacks def doQuery(calresource, uri): """ Run a query on the specified calendar collection accumulating the query responses. @param calresource: the L{CalDAVResource} for a calendar collection. @param uri: the uri for the calendar collection resource. """ @inlineCallbacks def queryCalendarObjectResource(resource, uri, name, calendar, timezone, query_ok=False, isowner=True): """ Run a query on the specified calendar. @param resource: the L{CalDAVResource} for the calendar. @param uri: the uri of the resource. @param name: the name of the resource. @param calendar: the L{Component} calendar read from the resource. """ # Handle private events access restrictions if not isowner: access = resource.accessMode else: access = None if query_ok or filter.match(calendar, access): # Check size of results is within limit matchcount[0] += 1 if max_number_of_results[0] is not None and matchcount[0] > max_number_of_results[0]: raise NumberOfMatchesWithinLimits(max_number_of_results[0]) if name: href = davxml.HRef.fromString(joinURL(uri, name)) else: href = davxml.HRef.fromString(uri) try: yield report_common.responseForHref(request, responses, href, resource, propertiesForResource, props, isowner, calendar=calendar, timezone=timezone) except ConcurrentModification: # This can happen because of a race-condition between the # time we determine which resources exist and the deletion # of one of these resources in another request. In this # case, we ignore the now missing resource rather # than raise an error for the entire report. log.error("Missing resource during query: %s" % (href,)) # Check whether supplied resource is a calendar or a calendar object resource if calresource.isPseudoCalendarCollection(): # Get the timezone property from the collection if one was not set in the query, # and store in the query filter for later use has_prop = (yield calresource.hasProperty(CalendarTimeZone(), request)) timezone = query_timezone if query_tz is None and has_prop: tz = (yield calresource.readProperty(CalendarTimeZone(), request)) filter.settimezone(tz) timezone = tuple(tz.calendar().subcomponents())[0] # Do some optimization of access control calculation by determining any inherited ACLs outside of # the child resource loop and supply those to the checkPrivileges on each child. filteredaces = (yield calresource.inheritedACEsforChildren(request)) # Check private events access status isowner = (yield calresource.isOwner(request)) # Check for disabled access if filteredaces is not None: index_query_ok = True try: # Get list of children that match the search and have read access names = [name for name, ignore_uid, ignore_type in (yield calresource.search(filter))] except IndexedSearchException: names = yield calresource.listChildren() index_query_ok = False if not names: returnValue(True) # Now determine which valid resources are readable and which are not ok_resources = [] yield calresource.findChildrenFaster( "1", request, lambda x, y: ok_resources.append((x, y)), None, None, None, names, (davxml.Read(),), inherited_aces=filteredaces ) for child, child_uri in ok_resources: child_uri_name = child_uri[child_uri.rfind("/") + 1:] if generate_calendar_data or not index_query_ok: calendar = (yield child.iCalendarForUser(request)) assert calendar is not None, "Calendar %s is missing from calendar collection %r" % (child_uri_name, self) else: calendar = None yield queryCalendarObjectResource(child, uri, child_uri_name, calendar, timezone, query_ok=index_query_ok, isowner=isowner) else: # Get the timezone property from the collection if one was not set in the query, # and store in the query object for later use timezone = query_timezone if query_tz is None: parent = (yield calresource.locateParent(request, uri)) assert parent is not None and parent.isPseudoCalendarCollection() has_prop = (yield parent.hasProperty(CalendarTimeZone(), request)) if has_prop: tz = (yield parent.readProperty(CalendarTimeZone(), request)) filter.settimezone(tz) timezone = tuple(tz.calendar().subcomponents())[0] # Check private events access status isowner = (yield calresource.isOwner(request)) calendar = (yield calresource.iCalendarForUser(request)) yield queryCalendarObjectResource(calresource, uri, None, calendar, timezone) returnValue(True) # Run report taking depth into account try: depth = request.headers.getHeader("depth", "0") yield report_common.applyToCalendarCollections(self, request, request.uri, depth, doQuery, (davxml.Read(),)) except TooManyInstancesError, ex: log.error("Too many instances need to be computed in calendar-query report") raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, MaxInstances.fromString(str(ex.max_allowed)), "Too many instances", ))
def doQuery(calresource, uri): """ Run a query on the specified calendar collection accumulating the query responses. @param calresource: the L{CalDAVResource} for a calendar collection. @param uri: the uri for the calendar collection resource. """ @inlineCallbacks def queryCalendarObjectResource(resource, uri, name, calendar, timezone, query_ok=False, isowner=True): """ Run a query on the specified calendar. @param resource: the L{CalDAVResource} for the calendar. @param uri: the uri of the resource. @param name: the name of the resource. @param calendar: the L{Component} calendar read from the resource. """ # Handle private events access restrictions if not isowner: access = resource.accessMode else: access = None if query_ok or filter.match(calendar, access): # Check size of results is within limit matchcount[0] += 1 if max_number_of_results[0] is not None and matchcount[0] > max_number_of_results[0]: raise NumberOfMatchesWithinLimits(max_number_of_results[0]) if name: href = davxml.HRef.fromString(joinURL(uri, name)) else: href = davxml.HRef.fromString(uri) try: yield report_common.responseForHref(request, responses, href, resource, propertiesForResource, props, isowner, calendar=calendar, timezone=timezone) except ConcurrentModification: # This can happen because of a race-condition between the # time we determine which resources exist and the deletion # of one of these resources in another request. In this # case, we ignore the now missing resource rather # than raise an error for the entire report. log.error("Missing resource during query: %s" % (href,)) # Check whether supplied resource is a calendar or a calendar object resource if calresource.isPseudoCalendarCollection(): # Get the timezone property from the collection if one was not set in the query, # and store in the query filter for later use has_prop = (yield calresource.hasProperty(CalendarTimeZone(), request)) timezone = query_timezone if query_tz is None and has_prop: tz = (yield calresource.readProperty(CalendarTimeZone(), request)) filter.settimezone(tz) timezone = tuple(tz.calendar().subcomponents())[0] # Do some optimization of access control calculation by determining any inherited ACLs outside of # the child resource loop and supply those to the checkPrivileges on each child. filteredaces = (yield calresource.inheritedACEsforChildren(request)) # Check private events access status isowner = (yield calresource.isOwner(request)) # Check for disabled access if filteredaces is not None: index_query_ok = True try: # Get list of children that match the search and have read access names = [name for name, ignore_uid, ignore_type in (yield calresource.search(filter))] except IndexedSearchException: names = yield calresource.listChildren() index_query_ok = False if not names: returnValue(True) # Now determine which valid resources are readable and which are not ok_resources = [] yield calresource.findChildrenFaster( "1", request, lambda x, y: ok_resources.append((x, y)), None, None, None, names, (davxml.Read(),), inherited_aces=filteredaces ) for child, child_uri in ok_resources: child_uri_name = child_uri[child_uri.rfind("/") + 1:] if generate_calendar_data or not index_query_ok: calendar = (yield child.iCalendarForUser(request)) assert calendar is not None, "Calendar %s is missing from calendar collection %r" % (child_uri_name, self) else: calendar = None yield queryCalendarObjectResource(child, uri, child_uri_name, calendar, timezone, query_ok=index_query_ok, isowner=isowner) else: # Get the timezone property from the collection if one was not set in the query, # and store in the query object for later use timezone = query_timezone if query_tz is None: parent = (yield calresource.locateParent(request, uri)) assert parent is not None and parent.isPseudoCalendarCollection() has_prop = (yield parent.hasProperty(CalendarTimeZone(), request)) if has_prop: tz = (yield parent.readProperty(CalendarTimeZone(), request)) filter.settimezone(tz) timezone = tuple(tz.calendar().subcomponents())[0] # Check private events access status isowner = (yield calresource.isOwner(request)) calendar = (yield calresource.iCalendarForUser(request)) yield queryCalendarObjectResource(calresource, uri, None, calendar, timezone) returnValue(True)
"calendar-proxy-write-for", ): # name is required to be str from twistedcaldav.directory.calendaruserproxy import ( CalendarUserProxyPrincipalResource) return succeed(CalendarUserProxyPrincipalResource(self, str(name))) else: return succeed(None) def listChildren(self): if config.EnableProxyPrincipals: return ( "calendar-proxy-read", "calendar-proxy-write", ) else: return () ## # Utilities ## authReadACL = davxml.ACL( # Read access for authenticated users. davxml.ACE( davxml.Principal(davxml.Authenticated()), davxml.Grant(davxml.Privilege(davxml.Read())), davxml.Protected(), ), )
def report_DAV__principal_property_search(self, request, principal_property_search): """ Generate a principal-property-search REPORT. (RFC 3744, section 9.4) """ # Verify root element if not isinstance(principal_property_search, element.PrincipalPropertySearch): raise ValueError("%s expected as root element, not %s." % (element.PrincipalPropertySearch.sname(), principal_property_search.sname())) # Only handle Depth: 0 depth = request.headers.getHeader("depth", "0") if depth != "0": log.error("Error in prinicpal-property-search REPORT, Depth set to %s" % (depth,)) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth,))) # Get a single DAV:prop element from the REPORT request body propertiesForResource = None propElement = None propertySearches = [] applyTo = False for child in principal_property_search.children: if child.qname() == (dav_namespace, "prop"): propertiesForResource = prop_common.propertyListForResource propElement = child elif child.qname() == (dav_namespace, "apply-to-principal-collection-set"): applyTo = True elif child.qname() == (dav_namespace, "property-search"): props = child.childOfType(element.PropertyContainer) props.removeWhitespaceNodes() match = child.childOfType(element.Match) propertySearches.append((props.children, str(match).lower())) def nodeMatch(node, match): """ See if the content of the supplied node matches the supplied text. Try to follow the matching guidance in rfc3744 section 9.4.1. @param prop: the property element to match. @param match: the text to match against. @return: True if the property matches, False otherwise. """ node.removeWhitespaceNodes() for child in node.children: if isinstance(child, PCDATAElement): comp = str(child).lower() if comp.find(match) != -1: return True else: return nodeMatch(child, match) else: return False def propertySearch(resource, request): """ Test the resource to see if it contains properties matching the property-search specification in this report. @param resource: the L{DAVFile} for the resource to test. @param request: the current request. @return: True if the resource has matching properties, False otherwise. """ for props, match in propertySearches: # Test each property for prop in props: try: propvalue = waitForDeferred(resource.readProperty(prop.qname(), request)) yield propvalue propvalue = propvalue.getResult() if propvalue and not nodeMatch(propvalue, match): yield False return except HTTPError: # No property => no match yield False return yield True propertySearch = deferredGenerator(propertySearch) # Run report try: resources = [] responses = [] matchcount = 0 if applyTo: for principalCollection in self.principalCollections(): uri = principalCollection.principalCollectionURL() resource = waitForDeferred(request.locateResource(uri)) yield resource resource = resource.getResult() if resource: resources.append((resource, uri)) else: resources.append((self, request.uri)) # Loop over all collections and principal resources within for resource, ruri in resources: # Do some optimisation of access control calculation by determining any inherited ACLs outside of # the child resource loop and supply those to the checkPrivileges on each child. filteredaces = waitForDeferred(resource.inheritedACEsforChildren(request)) yield filteredaces filteredaces = filteredaces.getResult() children = [] d = waitForDeferred(resource.findChildren("infinity", request, lambda x, y: children.append((x,y)), privileges=(element.Read(),), inherited_aces=filteredaces)) yield d d.getResult() for child, uri in children: if isPrincipalResource(child): d = waitForDeferred(propertySearch(child, request)) yield d d = d.getResult() if d: # Check size of results is within limit matchcount += 1 if matchcount > max_number_of_matches: raise NumberOfMatchesWithinLimits(max_number_of_matches) d = waitForDeferred(prop_common.responseForHref( request, responses, element.HRef.fromString(uri), child, propertiesForResource, propElement )) yield d d.getResult() except NumberOfMatchesWithinLimits: log.error("Too many matching components in prinicpal-property-search report") raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, element.NumberOfMatchesWithinLimits() )) yield MultiStatusResponse(responses)
def report_urn_ietf_params_xml_ns_carddav_addressbook_query( self, request, addressbook_query): """ Generate an addressbook-query REPORT. (CardDAV, section 8.6) """ # Verify root element if addressbook_query.qname() != (carddav_namespace, "addressbook-query"): raise ValueError( "{CardDAV:}addressbook-query expected as root element, not {elementName}." .format(elementName=addressbook_query.sname())) if not self.isCollection(): parent = (yield self.locateParent(request, request.uri)) if not parent.isAddressBookCollection(): log.error( "addressbook-query report is not allowed on a resource outside of an address book collection {parent}", parent=self) raise HTTPError( StatusResponse( responsecode.FORBIDDEN, "Must be address book collection or address book resource") ) responses = [] xmlfilter = addressbook_query.filter filter = Filter(xmlfilter) query = addressbook_query.props limit = addressbook_query.limit assert query is not None if query.qname() == ("DAV:", "allprop"): propertiesForResource = report_common.allPropertiesForResource generate_address_data = False elif query.qname() == ("DAV:", "propname"): propertiesForResource = report_common.propertyNamesForResource generate_address_data = False elif query.qname() == ("DAV:", "prop"): propertiesForResource = report_common.propertyListForResource # Verify that any address-data element matches what we can handle result, message, generate_address_data = report_common.validPropertyListAddressDataTypeVersion( query) if not result: log.error(message) raise HTTPError( ErrorResponse( responsecode.FORBIDDEN, (carddav_namespace, "supported-address-data"), "Invalid address-data", )) else: raise AssertionError("We shouldn't be here") # Verify that the filter element is valid if (filter is None) or not filter.valid(): log.error("Invalid filter element: %r" % (filter, )) raise HTTPError( ErrorResponse( responsecode.FORBIDDEN, (carddav_namespace, "valid-filter"), "Invalid filter element", )) matchcount = [ 0, ] max_number_of_results = [ config.MaxQueryWithDataResults if generate_address_data else None, ] limited = [ False, ] if limit: clientLimit = int(str(limit.childOfType(NResults))) if max_number_of_results[ 0] is None or clientLimit < max_number_of_results[0]: max_number_of_results[0] = clientLimit @inlineCallbacks def doQuery(addrresource, uri): """ Run a query on the specified address book collection accumulating the query responses. @param addrresource: the L{CalDAVResource} for an address book collection. @param uri: the uri for the address book collecton resource. """ def checkMaxResults(): matchcount[0] += 1 if max_number_of_results[0] is not None and matchcount[ 0] > max_number_of_results[0]: raise NumberOfMatchesWithinLimits(max_number_of_results[0]) @inlineCallbacks def queryAddressBookObjectResource(resource, uri, name, vcard, query_ok=False): """ Run a query on the specified vcard. @param resource: the L{CalDAVResource} for the vcard. @param uri: the uri of the resource. @param name: the name of the resource. @param vcard: the L{Component} vcard read from the resource. """ if query_ok or filter.match(vcard): # Check size of results is within limit checkMaxResults() if name: href = davxml.HRef.fromString(joinURL(uri, name)) else: href = davxml.HRef.fromString(uri) try: yield report_common.responseForHref(request, responses, href, resource, propertiesForResource, query, vcard=vcard) except ConcurrentModification: # This can happen because of a race-condition between the # time we determine which resources exist and the deletion # of one of these resources in another request. In this # case, we ignore the now missing resource rather # than raise an error for the entire report. log.error("Missing resource during sync: {href}", href=href) @inlineCallbacks def queryDirectoryBackedAddressBook(directoryBackedAddressBook, addressBookFilter): """ """ results, limited[ 0] = yield directoryBackedAddressBook.doAddressBookDirectoryQuery( addressBookFilter, query, max_number_of_results[0]) for vCardResult in results: # match against original filter if different from addressBookFilter if addressBookFilter is filter or filter.match( (yield vCardResult.vCard())): # Check size of results is within limit checkMaxResults() try: yield report_common.responseForHref( request, responses, vCardResult.hRef(), vCardResult, propertiesForResource, query, vcard=(yield vCardResult.vCard())) except ConcurrentModification: # This can happen because of a race-condition between the # time we determine which resources exist and the deletion # of one of these resources in another request. In this # case, we ignore the now missing resource rather # than raise an error for the entire report. log.error("Missing resource during sync: {href}", href=vCardResult.hRef()) if not addrresource.isAddressBookCollection(): #do UID lookup on last part of uri resource_name = urllib.unquote(uri[uri.rfind("/") + 1:]) if resource_name.endswith(".vcf") and len(resource_name) > 4: # see if parent is directory backed address book parent = (yield addrresource.locateParent(request, uri)) # Check whether supplied resource is an address book or an address book object resource if addrresource.isAddressBookCollection(): if addrresource.isDirectoryBackedAddressBookCollection(): yield maybeDeferred(queryDirectoryBackedAddressBook, addrresource, filter) else: # Do some optimisation of access control calculation by determining any inherited ACLs outside of # the child resource loop and supply those to the checkPrivileges on each child. filteredaces = (yield addrresource.inheritedACEsforChildren(request)) # Check for disabled access if filteredaces is not None: index_query_ok = True try: # Get list of children that match the search and have read access names = [ name for name, ignore_uid in ( yield addrresource.search(filter)) ] #@UnusedVariable except IndexedSearchException: names = yield addrresource.listChildren() index_query_ok = False if not names: return # Now determine which valid resources are readable and which are not ok_resources = [] yield addrresource.findChildrenFaster( "1", request, lambda x, y: ok_resources.append((x, y)), None, None, None, names, (davxml.Read(), ), inherited_aces=filteredaces) for child, child_uri in ok_resources: child_uri_name = child_uri[child_uri.rfind("/") + 1:] if generate_address_data or not index_query_ok: vcard = yield child.vCard() assert vcard is not None, "vCard {name} is missing from address book collection {collection!r}".format( name=child_uri_name, collection=self) else: vcard = None yield queryAddressBookObjectResource( child, uri, child_uri_name, vcard, query_ok=index_query_ok) else: handled = False resource_name = urllib.unquote(uri[uri.rfind("/") + 1:]) if resource_name.endswith(".vcf") and len(resource_name) > 4: # see if parent is directory backed address book parent = (yield addrresource.locateParent(request, uri)) if parent.isDirectoryBackedAddressBookCollection(): vCardFilter = carddavxml.Filter(*[ carddavxml.PropertyFilter( carddavxml.TextMatch.fromString( resource_name[:-4]), name="UID", # attributes ), ]) vCardFilter = Filter(vCardFilter) yield maybeDeferred(queryDirectoryBackedAddressBook, parent, vCardFilter) handled = True if not handled: vcard = yield addrresource.vCard() yield queryAddressBookObjectResource(addrresource, uri, None, vcard) if limited[0]: raise NumberOfMatchesWithinLimits(matchcount[0]) # Run report taking depth into account try: depth = request.headers.getHeader("depth", "0") yield report_common.applyToAddressBookCollections( self, request, request.uri, depth, doQuery, (davxml.Read(), )) except NumberOfMatchesWithinLimits, e: self.log.info( "Too many matching components in addressbook-query report. Limited to {limit} items", limit=e.maxLimit()) responses.append( davxml.StatusResponse( davxml.HRef.fromString(request.uri), davxml.Status.fromResponseCode( responsecode.INSUFFICIENT_STORAGE_SPACE), davxml.Error(davxml.NumberOfMatchesWithinLimits()), davxml.ResponseDescription( "Results limited to {limit} items".format( limit=e.maxLimit())), ))
def report_DAV__sync_collection(self, request, sync_collection): """ Generate a sync-collection REPORT. """ # These resource support the report if not config.EnableSyncReport or element.Report(element.SyncCollection(),) not in self.supportedReports(): log.error("sync-collection report is only allowed on calendar/inbox/addressbook/notification collection resources {s!r}", s=self) raise HTTPError(ErrorResponse( responsecode.FORBIDDEN, element.SupportedReport(), "Report not supported on this resource", )) responses = [] # Do not support limit if sync_collection.sync_limit is not None: raise HTTPError(ErrorResponse( responsecode.INSUFFICIENT_STORAGE_SPACE, element.NumberOfMatchesWithinLimits(), "Report limit not supported", )) # Process Depth and sync-level for backwards compatibility # Use sync-level if present and ignore Depth, else use Depth if sync_collection.sync_level: depth = sync_collection.sync_level if depth == "infinite": depth = "infinity" descriptor = "DAV:sync-level" else: depth = request.headers.getHeader("depth", None) descriptor = "Depth header without DAV:sync-level" if depth not in ("1", "infinity"): log.error("sync-collection report with invalid depth header: {d}", d=depth) raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Invalid %s value" % (descriptor,))) propertyreq = sync_collection.property.children if sync_collection.property else None # Do some optimization of access control calculation by determining any inherited ACLs outside of # the child resource loop and supply those to the checkPrivileges on each child. filteredaces = (yield self.inheritedACEsforChildren(request)) changed, removed, notallowed, newtoken, resourceChanged = yield self.whatchanged(sync_collection.sync_token, depth) # Now determine which valid resources are readable and which are not ok_resources = [] forbidden_resources = [] if changed: yield self.findChildrenFaster( depth, request, lambda x, y: ok_resources.append((x, y)), lambda x, y: forbidden_resources.append((x, y)), None, None, changed, (element.Read(),), inherited_aces=filteredaces ) if resourceChanged: ok_resources.append((self, request.uri)) for child, child_uri in ok_resources: href = element.HRef.fromString(child_uri) try: if propertyreq: yield responseForHref( request, responses, href, child, functools.partial(_namedPropertiesForResource, dataAllowed=False, forbidden=False), propertyreq) else: responses.append(element.StatusResponse(element.HRef.fromString(href), element.Status.fromResponseCode(responsecode.OK))) except ConcurrentModification: # This can happen because of a race-condition between the # time we determine which resources exist and the deletion # of one of these resources in another request. In this # case, we ignore the now missing resource rather # than raise an error for the entire report. log.error("Missing resource during sync: {h}", h=href) for child, child_uri in forbidden_resources: href = element.HRef.fromString(child_uri) try: if propertyreq: yield responseForHref( request, responses, href, child, functools.partial(_namedPropertiesForResource, dataAllowed=False, forbidden=True), propertyreq) else: responses.append(element.StatusResponse(element.HRef.fromString(href), element.Status.fromResponseCode(responsecode.OK))) except ConcurrentModification: # This can happen because of a race-condition between the # time we determine which resources exist and the deletion # of one of these resources in another request. In this # case, we ignore the now missing resource rather # than raise an error for the entire report. log.error("Missing resource during sync: {h}", h=href) for name in removed: href = element.HRef.fromString(joinURL(request.uri, name)) responses.append(element.StatusResponse(element.HRef.fromString(href), element.Status.fromResponseCode(responsecode.NOT_FOUND))) for name in notallowed: href = element.HRef.fromString(joinURL(request.uri, name)) responses.append(element.StatusResponse(element.HRef.fromString(href), element.Status.fromResponseCode(responsecode.NOT_ALLOWED))) if not hasattr(request, "extendedLogItems"): request.extendedLogItems = {} request.extendedLogItems["responses"] = len(responses) responses.append(element.SyncToken.fromString(newtoken)) returnValue(MultiStatusResponse(responses))
def getWikiACL(resource, request): """ Ask the wiki server we're paired with what level of access the authnUser has. Returns an ACL. Wiki authentication is a bit tricky because the end-user accessing a group calendar may not actually be enabled for calendaring. Therefore in that situation, the authzUser will have been replaced with the wiki principal in locateChild( ), so that any changes the user makes will have the wiki as the originator. The authnUser will always be the end-user. """ from twistedcaldav.directory.principal import DirectoryPrincipalResource if (not hasattr(resource, "record") or resource.record.recordType != RecordType.macOSXServerWiki): returnValue(None) if hasattr(request, 'wikiACL'): returnValue(request.wikiACL) wikiRecord = resource.record wikiID = wikiRecord.shortNames[0] userRecord = None try: url = request.authnUser.principalURL() principal = (yield request.locateResource(url)) if isinstance(principal, DirectoryPrincipalResource): userRecord = principal.record except: # TODO: better error handling pass try: access = yield wikiRecord.accessForRecord(userRecord) # The ACL we returns has ACEs for the end-user and the wiki principal # in case authzUser is the wiki principal. if access == WikiAccessLevel.read: request.wikiACL = davxml.ACL( davxml.ACE( (request.authnUser.principalElement() if request.authnUser is not None else davxml.Principal( davxml.Unauthenticated())), davxml.Grant( davxml.Privilege(davxml.Read()), davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()), # We allow write-properties so that direct sharees can # change e.g. calendar color properties davxml.Privilege(davxml.WriteProperties()), ), TwistedACLInheritable(), ), davxml.ACE( davxml.Principal( davxml.HRef.fromString( "/principals/wikis/{}/".format(wikiID))), davxml.Grant( davxml.Privilege(davxml.Read()), davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()), ), TwistedACLInheritable(), )) returnValue(request.wikiACL) elif access == WikiAccessLevel.write: request.wikiACL = davxml.ACL( davxml.ACE( (request.authnUser.principalElement() if request.authnUser is not None else davxml.Principal( davxml.Unauthenticated())), davxml.Grant( davxml.Privilege(davxml.Read()), davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()), davxml.Privilege(davxml.Write()), ), TwistedACLInheritable(), ), davxml.ACE( davxml.Principal( davxml.HRef.fromString( "/principals/wikis/{}/".format(wikiID))), davxml.Grant( davxml.Privilege(davxml.Read()), davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()), davxml.Privilege(davxml.Write()), ), TwistedACLInheritable(), )) returnValue(request.wikiACL) else: # "no-access": if userRecord is None: # Return a 401 so they have an opportunity to log in response = (yield UnauthorizedResponse.makeResponse( request.credentialFactories, request.remoteAddr, )) raise HTTPError(response) raise HTTPError( StatusResponse(responsecode.FORBIDDEN, "You are not allowed to access this wiki")) except HTTPError: # pass through the HTTPError we might have raised above raise except Exception as e: log.error("Wiki ACL lookup failed: {error}", error=e) raise HTTPError( StatusResponse(responsecode.SERVICE_UNAVAILABLE, "Wiki ACL lookup failed"))
def _processFBURL(self, request): # # Check authentication and access controls # yield self.authorize(request, (davxml.Read(),)) # Extract query parameters from the URL args = ('start', 'end', 'duration', 'token', 'format', 'user',) for arg in args: setattr(self, arg, request.args.get(arg, [None])[0]) # Some things we do not handle if self.token or self.user: raise HTTPError(ErrorResponse( responsecode.NOT_ACCEPTABLE, (calendarserver_namespace, "supported-query-parameter"), "Invalid query parameter", )) # Check format if self.format: self.format = self.format.split(";")[0] if self.format not in ("text/calendar", "text/plain"): raise HTTPError(ErrorResponse( responsecode.NOT_ACCEPTABLE, (calendarserver_namespace, "supported-format"), "Invalid return format requested", )) else: self.format = "text/calendar" # Start/end/duration must be valid iCalendar DATE-TIME UTC or DURATION values try: if self.start: self.start = DateTime.parseText(self.start) if not self.start.utc(): raise ValueError() if self.end: self.end = DateTime.parseText(self.end) if not self.end.utc(): raise ValueError() if self.duration: self.duration = Duration.parseText(self.duration) except ValueError: raise HTTPError(ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-query-parameters"), "Invalid query parameters", )) # Sanity check start/end/duration # End and duration cannot both be present if self.end and self.duration: raise HTTPError(ErrorResponse( responsecode.NOT_ACCEPTABLE, (calendarserver_namespace, "valid-query-parameters"), "Invalid query parameters", )) # Duration must be positive if self.duration and self.duration.getTotalSeconds() < 0: raise HTTPError(ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-query-parameters"), "Invalid query parameters", )) # Now fill in the missing pieces if self.start is None: self.start = DateTime.getNowUTC() self.start.setHHMMSS(0, 0, 0) if self.duration: self.end = self.start + self.duration if self.end is None: self.end = self.start + Duration(days=config.FreeBusyURL.TimePeriod) # End > start if self.end <= self.start: raise HTTPError(ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-query-parameters"), "Invalid query parameters", )) # TODO: We should probably verify that the actual time-range is within sensible bounds (e.g. not too far in the past or future and not too long) # Now lookup the principal details for the targeted user principal = (yield self.parent.principalForRecord()) # Pick the first mailto cu address or the first other type cuaddr = None for item in principal.calendarUserAddresses(): if cuaddr is None: cuaddr = item if item.startswith("mailto:"): cuaddr = item break # Get inbox details inboxURL = principal.scheduleInboxURL() if inboxURL is None: raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, "No schedule inbox URL for principal: %s" % (principal,))) try: inbox = (yield request.locateResource(inboxURL)) except: log.error("No schedule inbox for principal: {p}", p=principal) inbox = None if inbox is None: raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, "No schedule inbox for principal: %s" % (principal,))) organizer = recipient = LocalCalendarUser(cuaddr, principal.record) recipient.inbox = inbox._newStoreObject attendeeProp = Property("ATTENDEE", recipient.cuaddr) timerange = Period(self.start, self.end) fbresult = yield FreebusyQuery( organizer=organizer, recipient=recipient, attendeeProp=attendeeProp, timerange=timerange, ).generateAttendeeFreeBusyResponse() response = Response() response.stream = MemoryStream(str(fbresult)) response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (self.format,))) returnValue(response)
def report_DAV__acl_principal_prop_set(self, request, acl_prinicpal_prop_set): """ Generate an acl-prinicpal-prop-set REPORT. (RFC 3744, section 9.2) """ # Verify root element if not isinstance(acl_prinicpal_prop_set, davxml.ACLPrincipalPropSet): raise ValueError("%s expected as root element, not %s." % (davxml.ACLPrincipalPropSet.sname(), acl_prinicpal_prop_set.sname())) # Depth must be "0" depth = request.headers.getHeader("depth", "0") if depth != "0": log.error("Error in prinicpal-prop-set REPORT, Depth set to %s" % (depth, )) raise HTTPError( StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth, ))) # # Check authentication and access controls # x = waitForDeferred(self.authorize(request, (davxml.ReadACL(), ))) yield x x.getResult() # Get a single DAV:prop element from the REPORT request body propertiesForResource = None propElement = None for child in acl_prinicpal_prop_set.children: if child.qname() == ("DAV:", "prop"): if propertiesForResource is not None: log.error("Only one DAV:prop element allowed") raise HTTPError( StatusResponse(responsecode.BAD_REQUEST, "Only one DAV:prop element allowed")) propertiesForResource = prop_common.propertyListForResource propElement = child if propertiesForResource is None: log.error( "Error in acl-principal-prop-set REPORT, no DAV:prop element") raise HTTPError( StatusResponse(responsecode.BAD_REQUEST, "No DAV:prop element")) # Enumerate principals on ACL in current resource principals = [] acl = waitForDeferred(self.accessControlList(request)) yield acl acl = acl.getResult() for ace in acl.children: resolved = waitForDeferred( self.resolvePrincipal(ace.principal.children[0], request)) yield resolved resolved = resolved.getResult() if resolved is not None and resolved not in principals: principals.append(resolved) # Run report for each referenced principal try: responses = [] matchcount = 0 for principal in principals: # Check size of results is within limit matchcount += 1 if matchcount > max_number_of_matches: raise NumberOfMatchesWithinLimits(max_number_of_matches) resource = waitForDeferred(request.locateResource(str(principal))) yield resource resource = resource.getResult() if resource is not None: # # Check authentication and access controls # x = waitForDeferred( resource.authorize(request, (davxml.Read(), ))) yield x try: x.getResult() except HTTPError: responses.append( davxml.StatusResponse( principal, davxml.Status.fromResponseCode( responsecode.FORBIDDEN))) else: d = waitForDeferred( prop_common.responseForHref(request, responses, principal, resource, propertiesForResource, propElement)) yield d d.getResult() else: log.error("Requested principal resource not found: %s" % (str(principal), )) responses.append( davxml.StatusResponse( principal, davxml.Status.fromResponseCode( responsecode.NOT_FOUND))) except NumberOfMatchesWithinLimits: log.error("Too many matching components") raise HTTPError( ErrorResponse(responsecode.FORBIDDEN, davxml.NumberOfMatchesWithinLimits())) yield MultiStatusResponse(responses)