def test_public(self): data = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN X-CALENDARSERVER-ACCESS:PUBLIC BEGIN:VEVENT UID:12345-67890 DTSTART:20080601T120000Z DTEND:20080601T130000Z ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] ORGANIZER;CN=User 01:mailto:[email protected] END:VEVENT END:VCALENDAR """.replace("\n", "\r\n") for item in ( data, Component.fromString(data), ): self.assertEqual( str( PrivateEventFilter(Component.ACCESS_PUBLIC, True).filter(item)), data) self.assertEqual( str( PrivateEventFilter(Component.ACCESS_PUBLIC, False).filter(item)), data)
def test_confidential(self): data = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN X-CALENDARSERVER-ACCESS:CONFIDENTIAL BEGIN:VEVENT UID:12345-67890 DTSTART:20080601T120000Z DTEND:20080601T130000Z ATTENDEE:mailto:[email protected] ATTENDEE:mailto:[email protected] DESCRIPTION:In confidence LOCATION:My office ORGANIZER;CN=User 01:mailto:[email protected] SUMMARY:Confidential END:VEVENT END:VCALENDAR """.replace("\n", "\r\n") filtered = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN X-CALENDARSERVER-ACCESS:CONFIDENTIAL BEGIN:VEVENT UID:12345-67890 DTSTART:20080601T120000Z DTEND:20080601T130000Z END:VEVENT END:VCALENDAR """.replace("\n", "\r\n") for item in (data, Component.fromString(data),): self.assertEqual(str(PrivateEventFilter(Component.ACCESS_CONFIDENTIAL, True).filter(item)), data) self.assertEqual(str(PrivateEventFilter(Component.ACCESS_CONFIDENTIAL, False).filter(item)), filtered)
def filteredComponent(self, accessUID, asAdmin=False): """ Filter this calendar object's iCalendar component as it would be perceived by a particular user, accounting for per-user iCalendar data and private events, and return a L{Deferred} that fires with that object. Unlike the result of C{component()}, which contains storage-specific iCalendar properties, this is a valid iCalendar object which could be serialized and displayed to other iCalendar-processing software. @param accessUID: the UID of the principal who is accessing this component. @type accessUID: C{str} (UTF-8 encoded) @param asAdmin: should the given UID be treated as an administrator? If this is C{True}, the resulting component will have an unobscured view of private events, even if the given UID is not actually the owner of said events. (However, per-instance overridden values will still be seen as the given C{accessUID}.) @return: a L{Deferred} which fires with a L{twistedcaldav.ical.Component}. """ component = yield self.componentForUser(accessUID) calendar = self.calendar() isOwner = asAdmin or (calendar.owned() and calendar.ownerCalendarHome().uid() == accessUID) for data_filter in [ HiddenInstanceFilter(), PrivateEventFilter(self.accessMode, isOwner), ]: component = data_filter.filter(component) returnValue(component)
def _namedPropertiesForResource(request, props, resource, calendar=None, timezone=None, vcard=None, isowner=True, dataAllowed=True, forbidden=False): """ Return the specified properties on the specified resource. @param request: the L{IRequest} for the current request. @param props: a list of property elements or qname tuples for the properties of interest. @param resource: the L{CalDAVResource} for the targeted resource. @param calendar: the L{Component} for the calendar for the resource. This may be None if the calendar has not already been read in, in which case the resource will be used to get the calendar if needed. @param timezone: the L{Component} the VTIMEZONE to use for floating/all-day. @param vcard: the L{Component} for the vcard for the resource. This may be None if the vcard has not already been read in, in which case the resource will be used to get the vcard if needed. @param isowner: C{True} if the authorized principal making the request is the DAV:owner, C{False} otherwise. @param dataAllowed: C{True} if calendar/address data is allowed to be returned, C{False} otherwise. @param forbidden: if C{True} then return 403 status for all properties, C{False} otherwise. @return: a map of OK and NOT FOUND property values. """ properties_by_status = { responsecode.OK : [], responsecode.FORBIDDEN : [], responsecode.NOT_FOUND : [], } # Look for Prefer header first, then try Brief prefer = request.headers.getHeader("prefer", {}) returnMinimal = any([key == "return" and value == "minimal" for key, value, _ignore_args in prefer]) if not returnMinimal: returnMinimal = request.headers.getHeader("brief", False) for property in props: if isinstance(property, element.WebDAVElement): qname = property.qname() else: qname = property if forbidden: properties_by_status[responsecode.FORBIDDEN].append(propertyName(qname)) continue if isinstance(property, caldavxml.CalendarData): if dataAllowed: # Handle private events access restrictions if calendar is None: calendar = (yield resource.componentForUser()) filtered = HiddenInstanceFilter().filter(calendar) filtered = PrivateEventFilter(resource.accessMode, isowner).filter(filtered) filtered = CalendarDataFilter(property, timezone).filter(filtered) propvalue = CalendarData.fromCalendar(filtered, format=property.content_type) properties_by_status[responsecode.OK].append(propvalue) else: properties_by_status[responsecode.FORBIDDEN].append(propertyName(qname)) continue if isinstance(property, carddavxml.AddressData): if dataAllowed: if vcard is None: vcard = (yield resource.vCard()) filtered = AddressDataFilter(property).filter(vcard) propvalue = AddressData.fromAddress(filtered, format=property.content_type) properties_by_status[responsecode.OK].append(propvalue) else: properties_by_status[responsecode.FORBIDDEN].append(propertyName(qname)) continue has = (yield resource.hasProperty(property, request)) if has: try: prop = (yield resource.readProperty(property, request)) if prop is not None: properties_by_status[responsecode.OK].append(prop) elif not returnMinimal: properties_by_status[responsecode.NOT_FOUND].append(propertyName(qname)) except HTTPError: f = Failure() status = statusForFailure(f, "getting property: %s" % (qname,)) if status not in properties_by_status: properties_by_status[status] = [] if not returnMinimal or status != responsecode.NOT_FOUND: properties_by_status[status].append(propertyName(qname)) elif not returnMinimal: properties_by_status[responsecode.NOT_FOUND].append(propertyName(qname)) returnValue(properties_by_status)
def http_GET(self, request): if self.exists(): # Special sharing request on a calendar or address book if self.isCalendarCollection() or self.isAddressBookCollection(): # Check for action=share if request.args: action = request.args.get("action", ("",)) if len(action) != 1: raise HTTPError(ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-action"), "Invalid action parameter: %s" % (action,), )) action = action[0] dispatch = { "share": self.directShare, }.get(action, None) if dispatch is None: raise HTTPError(ErrorResponse( responsecode.BAD_REQUEST, (calendarserver_namespace, "supported-action"), "Action not supported: %s" % (action,), )) response = (yield dispatch(request)) returnValue(response) else: # FIXME: this should be implemented in storebridge.CalendarObject.render # Look for calendar access restriction on existing resource. parentURL = parentForURL(request.uri) parent = (yield request.locateResource(parentURL)) if isPseudoCalendarCollectionResource(parent): # Check authorization first yield self.authorize(request, (davxml.Read(),)) # Accept header handling accepted_type = bestAcceptType(request.headers.getHeader("accept"), Component.allowedTypes()) if accepted_type is None: raise HTTPError(StatusResponse(responsecode.NOT_ACCEPTABLE, "Cannot generate requested data type")) caldata = (yield self.componentForUser()) # Filter any attendee hidden instances caldata = HiddenInstanceFilter().filter(caldata) if self.accessMode: # Non DAV:owner's have limited access to the data isowner = (yield self.isOwner(request)) # Now "filter" the resource calendar data caldata = PrivateEventFilter(self.accessMode, isowner).filter(caldata) response = Response() response.stream = MemoryStream(caldata.getTextWithTimezones(includeTimezones=not config.EnableTimezonesByReference, format=accepted_type)) response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (accepted_type,))) # Add Schedule-Tag header if property is present if self.scheduleTag: response.headers.setHeader("Schedule-Tag", self.scheduleTag) returnValue(response) # Do normal GET behavior response = (yield super(CalDAVResource, self).http_GET(request)) returnValue(response)