text: # -*- test-case-name: twistedcaldav.directory.test.test_calendar -*-
  ##
  # Copyright (c) 2006-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.
  ##
  
  """
  Implements a directory-backed calendar hierarchy.
  """
  
  __all__ = [
      "DirectoryCalendarHomeProvisioningResource",
      "DirectoryCalendarHomeTypeProvisioningResource",
      "DirectoryCalendarHomeUIDProvisioningResource",
      "DirectoryCalendarHomeResource",
  ]
  
  from twext.python.log import Logger
  
  from txweb2 import responsecode
  from txweb2.auth.wrapper import UnauthorizedResponse
  from txweb2.dav.util import joinURL
  from txweb2.http import HTTPError, RedirectResponse
  from txweb2.http_headers import ETag, MimeType
  
  from twisted.internet.defer import succeed, inlineCallbacks, returnValue
  
  from twistedcaldav.config import config
  from twistedcaldav.directory.common import uidsResourceName, \
      CommonUIDProvisioningResource, CommonHomeTypeProvisioningResource
  from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVResource, \
      DAVResourceWithChildrenMixin
  from twistedcaldav.resource import CalendarHomeResource
  
  from txdav.who.wiki import getWikiACL
  
  from uuid import uuid4
  import urllib
  
  
  log = Logger()
  
  
  # FIXME: copied from resource.py to avoid circular dependency
  
  
  class CalDAVComplianceMixIn(object):
  
      def davComplianceClasses(self):
          return (
              tuple(super(CalDAVComplianceMixIn, self).davComplianceClasses()) +
              config.CalDAVComplianceClasses
          )
  
  
  class DirectoryCalendarProvisioningResource (
      ReadOnlyResourceMixIn,
      CalDAVComplianceMixIn,
      DAVResourceWithChildrenMixin,
      DAVResource,
  ):
  
      def defaultAccessControlList(self):
          return succeed(config.ProvisioningResourceACL)
  
      def etag(self):
          return succeed(ETag(str(uuid4())))
  
      def contentType(self):
          return MimeType("httpd", "unix-directory")
  
      @inlineCallbacks
      def handleMissingTrailingSlash(self, request):
          try:
              _ignore_authnUser, authzUser = yield self.authenticate(request)
          except Exception:
              authzUser = None
  
          # Turn 301 into 401
          if authzUser is None:
              response = (yield UnauthorizedResponse.makeResponse(
                  request.credentialFactories,
                  request.remoteAddr
              ))
              returnValue(response)
          else:
              response = RedirectResponse(
                  request.unparseURL(
                      path=urllib.quote(
                          urllib.unquote(request.path),
                          safe=':/') + '/'
                  )
              )
              returnValue(response)
  
  
  class DirectoryCalendarHomeProvisioningResource (DirectoryCalendarProvisioningResource):
      """
      Resource which provisions calendar home collections as needed.
      """
  
      def __init__(self, directory, url, store):
          """
          @param directory: an L{IDirectoryService} to provision calendars from.
          @param url: the canonical URL for the resource.
          """
          assert directory is not None
          assert url.endswith("/"), "Collection URL must end in '/'"
  
          super(DirectoryCalendarHomeProvisioningResource, self).__init__()
  
          # MOVE2WHO
          self.directory = directory  # IDirectoryService(directory)
          self._url = url
          self._newStore = store
  
          #
          # Create children
          #
          # ...just users, locations, and resources though.  If we iterate all of
          # the directory's recordTypes, we also get the proxy sub principal types
          # and other things which don't have calendars.
  
          self.supportedChildTypes = (
              self.directory.recordType.user,
              self.directory.recordType.location,
              self.directory.recordType.resource,
          )
  
          for recordType, recordTypeName in [
              (r, self.directory.recordTypeToOldName(r)) for r in
              self.supportedChildTypes
          ]:
              self.putChild(
                  recordTypeName,
                  DirectoryCalendarHomeTypeProvisioningResource(
                      self, recordTypeName, recordType
                  )
              )
  
          self.putChild(uidsResourceName, DirectoryCalendarHomeUIDProvisioningResource(self))
  
      def url(self):
          return self._url
  
      def listChildren(self):
          return [
              self.directory.recordTypeToOldName(r) for r in
              self.supportedChildTypes
          ]
  
      def principalCollections(self):
          # FIXME: directory.principalCollection smells like a hack
          # See DirectoryPrincipalProvisioningResource.__init__()
          return self.directory.principalCollection.principalCollections()
  
      def principalForRecord(self, record):
          # FIXME: directory.principalCollection smells like a hack
          # See DirectoryPrincipalProvisioningResource.__init__()
          return self.directory.principalCollection.principalForRecord(record)
  
      @inlineCallbacks
      def homeForDirectoryRecord(self, record, request):
          uidResource = yield self.getChild(uidsResourceName)
          if uidResource is None:
              returnValue(None)
          else:
              returnValue((yield uidResource.homeResourceForRecord(record, request)))
  
      ##
      # DAV
      ##
  
      def isCollection(self):
          return True
  
      def displayName(self):
          return "calendars"
  
  
  class DirectoryCalendarHomeTypeProvisioningResource(
      CommonHomeTypeProvisioningResource,
      DirectoryCalendarProvisioningResource
  ):
      """
      Resource which provisions calendar home collections of a specific
      record type as needed.
      """
  
      def __init__(self, parent, name, recordType):
          """
          @param parent: the parent of this resource
          @param recordType: the directory record type to provision.
          """
          assert parent is not None
          assert name is not None
          assert recordType is not None
  
          super(DirectoryCalendarHomeTypeProvisioningResource, self).__init__()
  
          self.directory = parent.directory
          self.name = name
          self.recordType = recordType
          self._parent = parent
  
      def url(self):
          return joinURL(self._parent.url(), self.name)
  
      @inlineCallbacks
      def listChildren(self):
          if config.EnablePrincipalListings:
              children = []
              for record in (yield self.directory.recordsWithRecordType(self.recordType)):
                  if record.hasCalendars:
                      for shortName in record.shortNames:
                          children.append(shortName)
              returnValue(children)
          else:
              # Not a listable collection
              raise HTTPError(responsecode.FORBIDDEN)
  
      def makeChild(self, name):
          return None
  
      ##
      # DAV
      ##
  
      def isCollection(self):
          return True
  
      def displayName(self):
          return self.name
  
      ##
      # ACL
      ##
  
      def principalCollections(self):
          return self._parent.principalCollections()
  
      def principalForRecord(self, record):
          return self._parent.principalForRecord(record)
  
  
  class DirectoryCalendarHomeUIDProvisioningResource (
      CommonUIDProvisioningResource,
      DirectoryCalendarProvisioningResource
  ):
  
      homeResourceTypeName = 'calendars'
  
      enabledAttribute = 'hasCalendars'
  
      def homeResourceCreator(self, record, transaction):
          return DirectoryCalendarHomeResource.createHomeResource(
              self, record, transaction)
  
  
  class DirectoryCalendarHomeResource (CalendarHomeResource):
      """
      Calendar home collection resource.
      """
  
      @classmethod
      @inlineCallbacks
      def createHomeResource(cls, parent, record, transaction):
          self = yield super(DirectoryCalendarHomeResource, cls).createHomeResource(
              parent, record.uid, transaction)
          self.record = record
          returnValue(self)
  
      # Special ACLs for Wiki service
      def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
          def gotACL(wikiACL):
              if wikiACL is not None:
                  # ACL depends on wiki server...
                  log.debug("Wiki ACL: {acl}", acl=wikiACL.toxml())
                  return succeed(wikiACL)
              else:
                  # ...otherwise permissions are fixed, and are not subject to
                  # inheritance rules, etc.
                  return self.defaultAccessControlList()
  
          d = getWikiACL(self, request)
          d.addCallback(gotACL)
          return d
  
      def principalForRecord(self):
          return self.parent.principalForRecord(self.record)
  