[CalendarServer-changes] [5408] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Fri Mar 26 21:09:17 PDT 2010


Revision: 5408
          http://trac.macosforge.org/projects/calendarserver/changeset/5408
Author:   sagen at apple.com
Date:     2010-03-26 21:09:15 -0700 (Fri, 26 Mar 2010)
Log Message:
-----------
Adds the event-deletion portion of user deprovisioning.

Modified Paths:
--------------
    CalendarServer/trunk/bin/calendarserver_purge_events
    CalendarServer/trunk/calendarserver/tools/purge.py
    CalendarServer/trunk/calendarserver/tools/test/test_purge.py
    CalendarServer/trunk/twistedcaldav/test/util.py

Added Paths:
-----------
    CalendarServer/trunk/calendarserver/tools/test/deprovision/
    CalendarServer/trunk/calendarserver/tools/test/deprovision/augments.xml
    CalendarServer/trunk/calendarserver/tools/test/deprovision/caldavd.plist
    CalendarServer/trunk/calendarserver/tools/test/deprovision/resources-locations.xml
    CalendarServer/trunk/calendarserver/tools/test/deprovision/users-groups.xml

Modified: CalendarServer/trunk/bin/calendarserver_purge_events
===================================================================
--- CalendarServer/trunk/bin/calendarserver_purge_events	2010-03-26 10:27:26 UTC (rev 5407)
+++ CalendarServer/trunk/bin/calendarserver_purge_events	2010-03-27 04:09:15 UTC (rev 5408)
@@ -40,5 +40,5 @@
 
         sys.argv[1:1] = ["-f", join(home, "conf", "caldavd-dev.plist")]
 
-    from calendarserver.tools.purge import main
-    main()
+    from calendarserver.tools.purge import main_purge
+    main_purge()

Modified: CalendarServer/trunk/calendarserver/tools/purge.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/purge.py	2010-03-26 10:27:26 UTC (rev 5407)
+++ CalendarServer/trunk/calendarserver/tools/purge.py	2010-03-27 04:09:15 UTC (rev 5408)
@@ -16,28 +16,29 @@
 # limitations under the License.
 ##
 
-from pwd import getpwnam
-from twisted.python.util import switchUID
-from twistedcaldav.directory.directory import DirectoryError
-from grp import getgrnam
 from calendarserver.tap.util import FakeRequest
 from calendarserver.tap.util import getRootResource
 from calendarserver.tools.util import loadConfig, setupMemcached, setupNotifications
-from datetime import date, timedelta
+from datetime import date, timedelta, datetime
 from getopt import getopt, GetoptError
+from grp import getgrnam
+from pwd import getpwnam
 from twext.python.log import Logger
+from twext.web2.dav import davxml
 from twisted.internet import reactor
 from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.python.util import switchUID
 from twistedcaldav import caldavxml
 from twistedcaldav.caldavxml import TimeRange
 from twistedcaldav.config import config, ConfigurationError
+from twistedcaldav.directory.directory import DirectoryError
 from twistedcaldav.method.delete_common import DeleteResource
 import os
 import sys
 
 log = Logger()
 
-def usage(e=None):
+def usage_purge(e=None):
 
     name = os.path.basename(sys.argv[0])
     print "usage: %s [options]" % (name,)
@@ -58,7 +59,7 @@
         sys.exit(0)
 
 
-def main():
+def main_purge():
 
     try:
         (optargs, args) = getopt(
@@ -71,7 +72,7 @@
             ],
         )
     except GetoptError, e:
-        usage(e)
+        usage_purge(e)
 
     #
     # Get configuration
@@ -83,14 +84,14 @@
 
     for opt, arg in optargs:
         if opt in ("-h", "--help"):
-            usage()
+            usage_purge()
 
         elif opt in ("-d", "--days"):
             try:
                 days = int(arg)
             except ValueError, e:
                 print "Invalid value for --days: %s" % (arg,)
-                usage(e)
+                usage_purge(e)
 
         elif opt in ("-v", "--verbose"):
             verbose = True
@@ -104,6 +105,8 @@
         else:
             raise NotImplementedError(opt)
 
+    cutoff = (date.today()-timedelta(days=days)).strftime("%Y%m%dT000000Z")
+
     try:
         loadConfig(configFileName)
 
@@ -127,22 +130,20 @@
         print "Error: %s" % (e,)
         return
 
-    cutoff = (date.today() - timedelta(days=days)).strftime("%Y%m%dT000000Z")
 
     #
     # Start the reactor
     #
-    reactor.callLater(0.1, purgeThenStop, directory, rootResource, cutoff,
-        verbose=verbose, dryrun=dryrun)
+    reactor.callLater(0.1, callThenStop, purgeOldEvents, directory,
+        rootResource, cutoff, verbose=verbose, dryrun=dryrun)
 
     reactor.run()
 
 @inlineCallbacks
-def purgeThenStop(directory, rootResource, cutoff, verbose=False, dryrun=False):
+def callThenStop(method, *args, **kwds):
     try:
-        count = (yield purgeOldEvents(directory, rootResource, cutoff,
-            verbose=verbose, dryrun=dryrun))
-        if dryrun:
+        count = (yield method(*args, **kwds))
+        if kwds.get("dryrun", False):
             print "Would have purged %d events" % (count,)
         else:
             print "Purged %d events" % (count,)
@@ -165,6 +166,9 @@
         print "Scanning calendar homes ...",
 
     records = []
+    calendars = root.getChild("calendars")
+    uidsFPath = calendars.fp.child("__uids__")
+
     if uidsFPath.exists():
         for firstFPath in uidsFPath.children():
             if len(firstFPath.basename()) == 2:
@@ -175,6 +179,7 @@
                             record = directory.recordWithUID(uid)
                             if record is not None:
                                 records.append(record)
+
     if verbose:
         print "%d calendar homes found" % (len(records),)
 
@@ -224,7 +229,8 @@
                     )
                     try:
                         if not dryrun:
-                            (yield deleteResource(root, collection, resource, uri))
+                            (yield deleteResource(root, collection, resource,
+                                uri, record.guid))
                         eventCount += 1
                         homeEventCount += 1
                     except Exception, e:
@@ -237,12 +243,74 @@
     returnValue(eventCount)
 
 
-def deleteResource(root, collection, resource, uri):
+def deleteResource(root, collection, resource, uri, guid, implicit=False):
     request = FakeRequest(root, "DELETE", uri)
+    request.authnUser = request.authzUser = davxml.Principal(
+        davxml.HRef.fromString("/principals/__uids__/%s/" % (guid,))
+    )
 
     # TODO: this seems hacky, even for a stub request:
     request._rememberResource(resource, uri)
 
     deleter = DeleteResource(request, resource, uri,
-        collection, "infinity", allowImplicitSchedule=False)
+        collection, "infinity", allowImplicitSchedule=implicit)
     return deleter.run()
+
+
+ at inlineCallbacks
+def purgeGUID(guid, directory, root):
+
+    # Does the record exist?
+    record = directory.recordWithGUID(guid)
+    if record is None:
+        # The user has already been removed from the directory service.  We
+        # need to fashion a temporary, fake record
+        # FIXME: implement the fake record
+        pass
+
+    principalCollection = directory.principalCollection
+    principal = principalCollection.principalForRecord(record)
+    calendarHome = principal.calendarHome()
+
+    # Anything in the past should be deleted without implicit scheduling
+    now = datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
+    filter =  caldavxml.Filter(
+          caldavxml.ComponentFilter(
+              caldavxml.ComponentFilter(
+                  TimeRange(start=now,),
+                  name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
+              ),
+              name="VCALENDAR",
+           )
+      )
+
+    count = 0
+
+    for collName in calendarHome.listChildren():
+        collection = calendarHome.getChild(collName)
+        if collection.isCalendarCollection():
+
+            # To compute past and ongoing events...
+
+            # ...first start with all events...
+            allEvents = set(collection.listChildren())
+
+            ongoingEvents = set()
+
+            # ...and find those that appear *after* the given cutoff
+            for name, uid, type in collection.index().indexedSearch(filter):
+                ongoingEvents.add(name)
+
+            for name in allEvents:
+                resource = collection.getChild(name)
+                uri = "/calendars/__uids__/%s/%s/%s" % (
+                    record.uid,
+                    collName,
+                    name
+                )
+
+                (yield deleteResource(root, collection, resource,
+                    uri, guid, implicit=(name in ongoingEvents)))
+                count += 1
+
+    returnValue(count)

Added: CalendarServer/trunk/calendarserver/tools/test/deprovision/augments.xml
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/deprovision/augments.xml	                        (rev 0)
+++ CalendarServer/trunk/calendarserver/tools/test/deprovision/augments.xml	2010-03-27 04:09:15 UTC (rev 5408)
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2009-2010 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.
+ -->
+
+<!DOCTYPE accounts SYSTEM "../../../conf/auth/augments.dtd">
+
+<augments>
+  <record>
+    <uid>E9E78C86-4829-4520-A35D-70DDADAB2092</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+  </record>
+  <record>
+    <uid>291C2C29-B663-4342-8EA1-A055E6A04D65</uid>
+    <enable>true</enable>
+    <enable-calendar>true</enable-calendar>
+  </record>
+</augments>

Added: CalendarServer/trunk/calendarserver/tools/test/deprovision/caldavd.plist
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/deprovision/caldavd.plist	                        (rev 0)
+++ CalendarServer/trunk/calendarserver/tools/test/deprovision/caldavd.plist	2010-03-27 04:09:15 UTC (rev 5408)
@@ -0,0 +1,767 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+    Copyright (c) 2006-2010 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.
+  -->
+
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+  <dict>
+
+    <!--
+        Public network address information
+
+        This is the server's public network address, which is provided to
+        clients in URLs and the like.  It may or may not be the network
+        address that the server is listening to directly, though it is by
+        default.  For example, it may be the address of a load balancer or
+        proxy which forwards connections to the server.
+      -->
+
+    <!-- Network host name [empty = system host name] -->
+    <key>ServerHostName</key>
+    <string></string> <!-- The hostname clients use when connecting -->
+
+    <!-- HTTP port [0 = disable HTTP] -->
+    <key>HTTPPort</key>
+    <integer>8008</integer>
+
+    <!-- SSL port [0 = disable HTTPS] -->
+    <!-- (Must also configure SSLCertificate and SSLPrivateKey below) -->
+    <key>SSLPort</key>
+    <integer>8443</integer>
+
+    <!-- Redirect non-SSL ports to an SSL port -->
+    <key>RedirectHTTPToHTTPS</key>
+    <false/>
+
+    <!--
+        Network address configuration information
+
+        This configures the actual network address that the server binds to.
+      -->
+
+    <!-- List of IP addresses to bind to [empty = all] -->
+    <key>BindAddresses</key>
+    <array>
+    </array>
+
+    <!-- List of port numbers to bind to for HTTP [empty = same as "Port"] -->
+    <key>BindHTTPPorts</key>
+    <array>
+    </array>
+
+    <!-- List of port numbers to bind to for SSL [empty = same as "SSLPort"] -->
+    <key>BindSSLPorts</key>
+    <array>
+    </array>
+
+
+    <!--
+        Data Store
+      -->
+
+    <!-- Server root -->
+    <key>ServerRoot</key>
+    <string>%(ServerRoot)s</string>
+
+    <!-- Data root -->
+    <key>DataRoot</key>
+    <string>Data</string>
+
+    <!-- Document root -->
+    <key>DocumentRoot</key>
+    <string>Documents</string>
+
+    <!-- Configuration root -->
+    <key>ConfigRoot</key>
+    <string>/etc/caldavd</string>
+
+    <!-- Log root -->
+    <key>LogRoot</key>
+    <string>/var/log/caldavd</string>
+
+    <!-- Run root -->
+    <key>RunRoot</key>
+    <string>/var/run</string>
+
+    <!-- Child aliases -->
+    <key>Aliases</key>
+    <dict>
+      <!--
+      <key>foo</key>
+      <dict>
+        <key>path</key>
+        <string>/path/to/foo</string>
+      </dict>
+       -->
+    </dict>
+
+    <!-- User quota (in bytes) -->
+    <key>UserQuota</key>
+    <integer>104857600</integer><!-- 100Mb -->
+
+    <!-- Attachment size limit (in bytes) -->
+    <key>MaximumAttachmentSize</key>
+    <integer>1048576</integer><!-- 1Mb -->
+
+    <!-- Maximum number of unique attendees per entire event -->
+    <!-- 0 for no limit -->
+    <key>MaxAttendeesPerInstance</key>
+    <integer>100</integer>
+
+    <!-- Maximum number of instances allowed for a single RRULE -->
+    <!-- 0 for no limit -->
+    <key>MaxInstancesForRRULE</key>
+    <integer>400</integer>
+
+
+    <!--
+        Directory service
+
+        A directory service provides information about principals (eg.
+        users, groups, locations and resources) to the server.
+
+        A variety of directory services are available for use.
+      -->
+
+    <!-- XML File Directory Service -->
+    <key>DirectoryService</key>
+    <dict>
+      <key>type</key>
+      <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
+      
+      <key>params</key>
+      <dict>
+        <key>xmlFile</key>
+        <string>accounts.xml</string>
+        <key>recordTypes</key>
+        <array>
+            <string>users</string>
+            <string>groups</string>
+        </array>
+      </dict>
+    </dict>
+
+    <!-- XML File Resource Service -->
+    <key>ResourceService</key>
+    <dict>
+      <key>Enabled</key>
+      <true/>
+      <key>type</key>
+      <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
+      
+      <key>params</key>
+      <dict>
+        <key>xmlFile</key>
+        <string>resources.xml</string>
+        <key>recordTypes</key>
+        <array>
+            <string>resources</string>
+            <string>locations</string>
+        </array>
+        <key>cacheTimeout</key>
+        <integer>30</integer>
+      </dict>
+    </dict>
+    
+    <!-- Open Directory Service (Mac OS X) -->
+    <!--
+    <key>DirectoryService</key>
+    <dict>
+      <key>type</key>
+      <string>twistedcaldav.directory.appleopendirectory.OpenDirectoryService</string>
+      
+      <key>params</key>
+      <dict>
+        <key>node</key>
+        <string>/Search</string>
+        <key>cacheTimeout</key>
+        <integer>30</integer>
+      </dict>
+    </dict>
+    -->
+
+    <!--
+        Augment service
+
+        Augments for the directory service records to add calendar specific attributes.
+
+        A variety of augment services are available for use.
+        When using a partitioned server, a service that can be accessed from each host will be needed.
+      -->
+
+    <!-- XML File Augment Service -->
+    <key>AugmentService</key>
+    <dict>
+      <key>type</key>
+      <string>twistedcaldav.directory.augment.AugmentXMLDB</string>
+      
+      <key>params</key>
+      <dict>
+        <key>xmlFiles</key>
+        <array>
+	      <string>augments.xml</string>
+        </array>
+      </dict>
+    </dict>
+    
+    <!-- Sqlite Augment Service -->
+    <!--
+    <key>AugmentService</key>
+    <dict>
+      <key>type</key>
+      <string>twistedcaldav.directory.augment.AugmentSqliteDB</string>
+      
+      <key>params</key>
+      <dict>
+        <key>dbpath</key>
+        <string>/etc/caldavd/augments.sqlite</string>
+      </dict>
+    </dict>
+     -->
+
+    <!-- PostgreSQL Augment Service -->
+    <!--
+    <key>AugmentService</key>
+    <dict>
+      <key>type</key>
+      <string>twistedcaldav.directory.augment.AugmentPostgreSQLDB</string>
+      
+      <key>params</key>
+      <dict>
+        <key>host</key>
+        <string>localhost</string>
+        <key>database</key>
+        <string>augments</string>
+      </dict>
+    </dict>
+     -->
+
+    <!-- Sqlite ProxyDB Service -->
+    <key>ProxyDBService</key>
+    <dict>
+      <key>type</key>
+      <string>twistedcaldav.directory.calendaruserproxy.ProxySqliteDB</string>
+      
+      <key>params</key>
+      <dict>
+        <key>dbpath</key>
+        <string>proxies.sqlite</string>
+      </dict>
+    </dict>
+
+    <!-- PostgreSQL ProxyDB Service -->
+    <!--
+    <key>ProxyDBService</key>
+    <dict>
+      <key>type</key>
+      <string>twistedcaldav.directory.calendaruserproxy.ProxyPostgreSQLDB</string>
+      
+      <key>params</key>
+      <dict>
+        <key>host</key>
+        <string>localhost</string>
+        <key>database</key>
+        <string>proxies</string>
+      </dict>
+    </dict>
+     -->
+
+	<key>ProxyLoadFromFile</key>
+    <string>conf/auth/proxies-test.xml</string>
+
+    <!--
+        Special principals
+
+        These principals are granted special access and/or perform
+        special roles on the server.
+      -->
+
+    <!-- Principals with "DAV:all" access (relative URLs) -->
+    <key>AdminPrincipals</key>
+    <array>
+      <string>/principals/__uids__/admin/</string>
+    </array>
+
+    <!-- Principals with "DAV:read" access (relative URLs) -->
+    <key>ReadPrincipals</key>
+    <array>
+      <!-- <string>/principals/__uids__/983C8238-FB6B-4D92-9242-89C0A39E5F81/</string> -->
+    </array>
+
+    <!-- Principals that can pose as other principals -->
+    <key>SudoersFile</key>
+    <string>conf/sudoers.plist</string>
+
+    <!-- Create "proxy access" principals -->
+    <key>EnableProxyPrincipals</key>
+    <true/>
+
+
+    <!--
+        Permissions
+      -->
+
+    <!-- Anonymous read access for root resource -->
+    <key>EnableAnonymousReadRoot</key>
+    <true/>
+
+    <!-- Anonymous read access for resource hierarchy -->
+    <key>EnableAnonymousReadNav</key>
+    <false/>
+
+    <!-- Enables directory listings for principals -->
+    <key>EnablePrincipalListings</key>
+    <true/>
+
+    <!-- Render calendar collections as a monolithic iCalendar object -->
+    <key>EnableMonolithicCalendars</key>
+    <true/>
+
+
+    <!--
+        Authentication
+      -->
+
+    <key>Authentication</key>
+    <dict>
+
+      <!-- Clear text; best avoided -->
+      <key>Basic</key>
+      <dict>
+        <key>Enabled</key>
+        <true/>
+      </dict>
+
+      <!-- Digest challenge/response -->
+      <key>Digest</key>
+      <dict>
+        <key>Enabled</key>
+        <true/>
+        <key>Algorithm</key>
+        <string>md5</string>
+        <key>Qop</key>
+        <string></string>
+      </dict>
+
+      <!-- Kerberos/SPNEGO -->
+      <key>Kerberos</key>
+      <dict>
+        <key>Enabled</key>
+        <false/>
+        <key>ServicePrincipal</key>
+        <string></string>
+      </dict>
+
+      <!-- Wikiserver authentication (Mac OS X) -->
+      <key>Wiki</key>
+      <dict>
+        <key>Enabled</key>
+        <true/>
+        <key>Cookie</key>
+        <string>sessionID</string>
+        <key>URL</key>
+        <string>http://127.0.0.1/RPC2</string>
+        <key>UserMethod</key>
+        <string>userForSession</string>
+        <key>WikiMethod</key>
+        <string>accessLevelForUserWikiCalendar</string>
+      </dict>
+
+    </dict>
+
+
+    <!--
+        Logging
+      -->
+
+    <!-- Apache-style access log -->
+    <key>AccessLogFile</key>
+    <string>logs/access.log</string>
+    <key>RotateAccessLog</key>
+    <false/>
+
+    <!-- Server activity log -->
+    <key>ErrorLogFile</key>
+    <string>logs/error.log</string>
+
+    <!-- Log levels -->
+    <key>DefaultLogLevel</key>
+    <string>warn</string> <!-- debug, info, warn, error -->
+
+    <!-- Log level overrides for specific functionality -->
+    <key>LogLevels</key>
+    <dict>
+      <!--
+      <key>twistedcaldav.directory.appleopendirectory.OpenDirectoryService</key>
+      <string>debug</string>
+      -->
+    </dict>
+
+    <!-- Global server stats --> 
+    <key>GlobalStatsSocket</key> 
+    <string>logs/caldavd-stats.sock</string> 
+
+    <!-- Global server stats logging period --> 
+    <key>GlobalStatsLoggingPeriod</key> 
+    <integer>60</integer> 
+
+    <!-- Global server stats logging frequency [0 = disable stats] --> 
+    <key>GlobalStatsLoggingFrequency</key> 
+    <integer>12</integer>
+
+    <!-- Server process ID file -->
+    <key>PIDFile</key>
+    <string>logs/caldavd.pid</string>
+
+
+    <!--
+        Accounting
+      -->
+
+    <!-- Enable accounting for certain operations -->
+    <key>AccountingCategories</key>
+    <dict>
+      <key>iTIP</key>
+      <false/>
+      <key>HTTP</key>
+      <false/>
+    </dict>
+    <!-- Enable accounting for specific principals -->
+    <key>AccountingPrincipals</key>
+    <array>
+      <!-- <string>/principals/__uids__/454D85C0-09F0-4DC6-A3C6-97DFEB4622CD/</string> -->
+    </array>
+
+
+    <!--
+        SSL/TLS
+      -->
+
+    <!-- Public key -->
+    <key>SSLCertificate</key>
+    <string>twistedcaldav/test/data/server.pem</string>
+
+    <!-- SSL authority chain (for intermediate certs) -->
+    <key>SSLAuthorityChain</key>
+    <string></string>
+
+    <!-- Private key -->
+    <key>SSLPrivateKey</key>
+    <string>twistedcaldav/test/data/server.pem</string>
+
+
+    <!--
+        Process management
+      -->
+
+    <key>UserName</key>
+    <string></string>
+
+    <key>GroupName</key>
+    <string></string>
+
+    <key>ProcessType</key>
+    <string>Combined</string>
+
+    <key>MultiProcess</key>
+    <dict>
+      <key>ProcessCount</key>
+      <integer>2</integer> <!-- 0 = larger of: 4 or (2 * CPU count) -->
+    </dict>
+
+
+    <!--
+        Notifications
+      -->
+
+    <key>Notifications</key>
+    <dict>
+      <!-- Time spent coalescing notifications before delivery -->
+      <key>CoalesceSeconds</key>
+      <integer>3</integer>
+
+      <key>InternalNotificationHost</key>
+      <string>localhost</string>
+
+      <key>InternalNotificationPort</key>
+      <integer>62309</integer>
+
+      <key>Services</key>
+      <dict>
+        <key>SimpleLineNotifier</key>
+        <dict>
+          <!-- Simple line notification service (for testing) -->
+          <key>Service</key>
+          <string>twistedcaldav.notify.SimpleLineNotifierService</string>
+          <key>Enabled</key>
+          <false/>
+          <key>Port</key>
+          <integer>62308</integer>
+        </dict>
+
+        <key>XMPPNotifier</key>
+        <dict>
+          <!-- XMPP notification service -->
+          <key>Service</key>
+          <string>twistedcaldav.notify.XMPPNotifierService</string>
+          <key>Enabled</key>
+          <false/>
+
+          <!-- XMPP host and port to contact -->
+          <key>Host</key>
+          <string>xmpp.host.name</string>
+          <key>Port</key>
+          <integer>5222</integer>
+
+          <!-- Jabber ID and password for the server -->
+          <key>JID</key>
+          <string>jid at xmpp.host.name/resource</string>
+          <key>Password</key>
+          <string>password_goes_here</string>
+
+          <!-- PubSub service address -->
+          <key>ServiceAddress</key>
+          <string>pubsub.xmpp.host.name</string>
+
+          <key>NodeConfiguration</key>
+          <dict>
+            <key>pubsub#deliver_payloads</key>
+            <string>1</string>
+            <key>pubsub#persist_items</key>
+            <string>1</string>
+          </dict>
+
+          <!-- Sends a presence notification to XMPP server at this interval (prevents disconnect) -->
+          <key>KeepAliveSeconds</key>
+          <integer>120</integer>
+
+          <!-- Sends a pubsub publish to a particular heartbeat node at this interval -->
+          <key>HeartbeatMinutes</key>
+          <integer>30</integer>
+
+          <!-- List of glob-like expressions defining which XMPP JIDs can converse with the server (for debugging) -->
+          <key>AllowedJIDs</key>
+          <array>
+            <!--
+            <string>*.example.com</string>
+             -->
+          </array>
+        </dict>
+      </dict>
+    </dict>
+
+
+    <!--
+        Server-to-server protocol
+      -->
+
+    <key>Scheduling</key>
+    <dict>
+
+      <!-- CalDAV protocol options -->
+      <key>CalDAV</key>
+      <dict>
+        <key>EmailDomain</key>
+        <string></string>
+        <key>HTTPDomain</key>
+        <string></string>
+        <key>AddressPatterns</key>
+        <array>
+        </array>
+        <key>OldDraftCompatibility</key>
+        <true/>
+        <key>ScheduleTagCompatibility</key>
+        <true/>
+        <key>EnablePrivateComments</key>
+        <true/>
+      </dict>
+
+      <!-- iSchedule protocol options -->
+      <key>iSchedule</key>
+      <dict>
+        <key>Enabled</key>
+        <false/>
+        <key>AddressPatterns</key>
+        <array>
+        </array>
+        <key>Servers</key>
+        <string>conf/servertoserver-test.xml</string>
+      </dict>
+
+      <!-- iMIP protocol options -->
+      <key>iMIP</key>
+      <dict>
+        <key>Enabled</key>
+        <false/>
+        <key>MailGatewayServer</key>
+        <string>localhost</string>
+        <key>MailGatewayPort</key>
+        <integer>62310</integer>
+        <key>Sending</key>
+        <dict>
+          <key>Server</key>
+          <string></string>
+          <key>Port</key>
+          <integer>587</integer>
+          <key>UseSSL</key>
+          <true/>
+          <key>Username</key>
+          <string></string>
+          <key>Password</key>
+          <string></string>
+          <key>Address</key>
+          <string></string> <!-- Address email will be sent from -->
+        </dict>
+        <key>Receiving</key>
+        <dict>
+          <key>Server</key>
+          <string></string>
+          <key>Port</key>
+          <integer>995</integer>
+          <key>Type</key>
+          <string></string> <!-- Either "pop" or "imap" -->
+          <key>UseSSL</key>
+          <true/>
+          <key>Username</key>
+          <string></string>
+          <key>Password</key>
+          <string></string>
+          <key>PollingSeconds</key>
+          <integer>30</integer>
+        </dict>
+        <key>AddressPatterns</key>
+        <array>
+          <string>mailto:.*</string>
+        </array>
+      </dict>
+
+	  <!-- General options for scheduling -->
+	  <key>Options</key>
+	  <dict>
+        <key>AllowGroupAsOrganizer</key>
+        <false/>
+        <key>AllowLocationAsOrganizer</key>
+        <false/>
+        <key>AllowResourceAsOrganizer</key>
+        <false/>
+       </dict>
+
+    </dict>
+
+
+    <!--
+        Free-busy URL protocol
+      -->
+
+    <key>FreeBusyURL</key>
+    <dict>
+      <key>Enabled</key>
+      <true/>
+      <key>TimePeriod</key>
+      <integer>14</integer>
+      <key>AnonymousAccess</key>
+      <false/>
+    </dict>
+
+
+    <!--
+        Non-standard CalDAV extensions
+      -->
+
+    <!-- Calendar Drop Box -->
+    <key>EnableDropBox</key>
+    <true/>
+
+    <!-- Private Events -->
+    <key>EnablePrivateEvents</key>
+    <true/>
+
+    <!-- Timezone Service -->
+    <key>EnableTimezoneService</key>
+    <true/>
+
+
+    <!--
+        Miscellaneous items
+      -->
+
+    <!-- Service ACLs (Mac OS X) -->
+    <key>EnableSACLs</key>
+    <false/>
+
+    <!-- Web-based administration -->
+    <key>EnableWebAdmin</key>
+    <true/>
+
+    <!-- Support for Content-Encoding compression options as specified in RFC2616 Section 3.5 -->
+    <key>ResponseCompression</key>
+    <false/>
+    
+    <!-- The retry-after value (in seconds) to return with a 503 error. -->
+    <key>HTTPRetryAfter</key>
+    <integer>180</integer>
+
+    <!-- A unix socket used for communication between the child and master processes.
+         An empty value tells the server to use a tcp socket instead. -->
+    <key>ControlSocket</key>
+    <string>logs/caldavd.sock</string>
+
+    <!-- Support for Memcached -->
+    <key>Memcached</key>
+    <dict>
+      <key>MaxClients</key>
+      <integer>5</integer>
+      <key>memcached</key>
+      <string>memcached</string> <!-- Find in PATH -->
+      <key>Options</key>
+      <array>
+        <!--<string>-vv</string>-->
+      </array>
+      <key>Pools</key>
+        <dict>
+        <key>Default</key>
+            <dict>
+                <key>ClientEnabled</key>
+                <false/>
+                <key>ServerEnabled</key>
+                <false/>
+            </dict>
+        </dict>
+    </dict>
+
+    <!--
+        Twisted
+      -->
+
+    <key>Twisted</key>
+    <dict>
+      <key>twistd</key>
+      <string>../Twisted/bin/twistd</string>
+    </dict>
+
+
+    <key>Localization</key>
+    <dict>
+      <key>LocalesDirectory</key>
+      <string>locales</string>
+      <key>Language</key>
+      <string>English</string>
+    </dict>
+
+
+  </dict>
+</plist>

Added: CalendarServer/trunk/calendarserver/tools/test/deprovision/resources-locations.xml
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/deprovision/resources-locations.xml	                        (rev 0)
+++ CalendarServer/trunk/calendarserver/tools/test/deprovision/resources-locations.xml	2010-03-27 04:09:15 UTC (rev 5408)
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2006-2010 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.
+ -->
+
+<!DOCTYPE accounts SYSTEM "accounts.dtd">
+
+<accounts realm="Test Realm">
+  <location repeat="10">
+    <uid>location%02d</uid>
+    <guid>location%02d</guid>
+    <password>location%02d</password>
+    <name>Room %02d</name>
+  </location>
+  <resource repeat="10">
+    <uid>resource%02d</uid>
+    <guid>resource%02d</guid>
+    <password>resource%02d</password>
+    <name>Resource %02d</name>
+  </resource>
+</accounts>

Added: CalendarServer/trunk/calendarserver/tools/test/deprovision/users-groups.xml
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/deprovision/users-groups.xml	                        (rev 0)
+++ CalendarServer/trunk/calendarserver/tools/test/deprovision/users-groups.xml	2010-03-27 04:09:15 UTC (rev 5408)
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2006-2010 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.
+ -->
+
+<!DOCTYPE accounts SYSTEM "accounts.dtd">
+
+<accounts realm="Test Realm">
+  <user>
+    <uid>deprovisioned</uid>
+    <guid>E9E78C86-4829-4520-A35D-70DDADAB2092</guid>
+    <password>test</password>
+    <name>Deprovisioned User</name>
+    <first-name>Deprovisioned</first-name>
+    <last-name>User</last-name>
+  </user>
+  <user>
+    <uid>keeper</uid>
+    <guid>291C2C29-B663-4342-8EA1-A055E6A04D65</guid>
+    <password>test</password>
+    <name>Keeper User</name>
+    <first-name>Keeper</first-name>
+    <last-name>User</last-name>
+  </user>
+</accounts>

Modified: CalendarServer/trunk/calendarserver/tools/test/test_purge.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_purge.py	2010-03-26 10:27:26 UTC (rev 5407)
+++ CalendarServer/trunk/calendarserver/tools/test/test_purge.py	2010-03-27 04:09:15 UTC (rev 5408)
@@ -14,13 +14,18 @@
 # limitations under the License.
 ##
 
+from calendarserver.tap.util import getRootResource
+from calendarserver.tools.purge import purgeOldEvents, purgeGUID
+from datetime import datetime, timedelta
+from twext.python.filepath import CachingFilePath as FilePath
+from twext.python.plistlib import readPlistFromString
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks, Deferred, returnValue
+from twistedcaldav.config import config
+from twistedcaldav.test.util import TestCase, CapturingProcessProtocol
 import os
+import xml
 import zlib
-from twistedcaldav.config import config
-from twistedcaldav.test.util import TestCase
-from twisted.internet.defer import inlineCallbacks
-from calendarserver.tap.util import getRootResource
-from calendarserver.tools.purge import purgeOldEvents
 
 resourceAttr = "WebDAV:{DAV:}resourcetype"
 collectionType = zlib.compress("""<?xml version='1.0' encoding='UTF-8'?>
@@ -41,7 +46,7 @@
         self.directory = self.rootResource.getDirectory()
 
     @inlineCallbacks
-    def test_purge(self):
+    def test_purgeOldEvents(self):
         before = {
             "calendars" : {
                 "__uids__" : {
@@ -329,3 +334,240 @@
 END:VCALENDAR
 """.replace("\n", "\r\n")
 
+
+
+
+class DeprovisionTestCase(TestCase):
+
+    def setUp(self):
+        super(DeprovisionTestCase, self).setUp()
+
+        testRoot = os.path.join(os.path.dirname(__file__), "deprovision")
+        templateName = os.path.join(testRoot, "caldavd.plist")
+        templateFile = open(templateName)
+        template = templateFile.read()
+        templateFile.close()
+
+        newConfig = template % {
+            "ServerRoot" : os.path.abspath(config.ServerRoot),
+        }
+        configFilePath = FilePath(os.path.join(config.ConfigRoot, "caldavd.plist"))
+        configFilePath.setContent(newConfig)
+
+        self.configFileName = configFilePath.path
+        config.load(self.configFileName)
+
+        os.makedirs(config.DataRoot)
+        os.makedirs(config.DocumentRoot)
+
+        origUsersFile = FilePath(os.path.join(os.path.dirname(__file__),
+            "deprovision", "users-groups.xml"))
+        copyUsersFile = FilePath(os.path.join(config.DataRoot, "accounts.xml"))
+        origUsersFile.copyTo(copyUsersFile)
+
+        origResourcesFile = FilePath(os.path.join(os.path.dirname(__file__),
+            "deprovision", "resources-locations.xml"))
+        copyResourcesFile = FilePath(os.path.join(config.DataRoot, "resources.xml"))
+        origResourcesFile.copyTo(copyResourcesFile)
+
+        origAugmentFile = FilePath(os.path.join(os.path.dirname(__file__),
+            "deprovision", "augments.xml"))
+        copyAugmentFile = FilePath(os.path.join(config.DataRoot, "augments.xml"))
+        origAugmentFile.copyTo(copyAugmentFile)
+
+        self.rootResource = getRootResource(config)
+        self.directory = self.rootResource.getDirectory()
+
+        # Make sure trial puts the reactor in the right state, by letting it
+        # run one reactor iteration.  (Ignore me, please.)
+        d = Deferred()
+        reactor.callLater(0, d.callback, True)
+        return d
+
+    @inlineCallbacks
+    def runCommand(self, command, error=False):
+        """
+        Run the given command by feeding it as standard input to
+        calendarserver_deprovision in a subprocess.
+        """
+        sourceRoot = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
+        python = os.path.join(sourceRoot, "python")
+        script = os.path.join(sourceRoot, "bin", "calendarserver_purge_guid")
+
+        args = [python, script, "-f", self.configFileName]
+        if error:
+            args.append("--error")
+
+        cwd = sourceRoot
+
+        deferred = Deferred()
+        reactor.spawnProcess(CapturingProcessProtocol(deferred, command), python, args, env=os.environ, path=cwd)
+        output = yield deferred
+        try:
+            plist = readPlistFromString(output)
+        except xml.parsers.expat.ExpatError, e:
+            print "Error (%s) parsing (%s)" % (e, output)
+            raise
+
+        returnValue(plist)
+
+    @inlineCallbacks
+    def test_purgeGUID(self):
+        # deprovision, add an event
+
+        # Deprovisioned user is E9E78C86-4829-4520-A35D-70DDADAB2092
+        # Keeper user is        291C2C29-B663-4342-8EA1-A055E6A04D65
+
+        before = {
+            "calendars" : {
+                "__uids__" : {
+                    "E9" : {
+                        "E7" : {
+                            "E9E78C86-4829-4520-A35D-70DDADAB2092" : {
+                                "calendar": {
+                                    "@xattrs" :
+                                    {
+                                        resourceAttr : collectionType,
+                                    },
+                                    "noninvite.ics": {
+                                        "@contents" : NON_INVITE_ICS,
+                                    },
+                                    "organizer.ics": {
+                                        "@contents" : ORGANIZER_ICS,
+                                    },
+                                    "attendee.ics": {
+                                        "@contents" : ATTENDEE_ICS,
+                                    },
+                                },
+                            },
+                        },
+                    },
+                    "29" : {
+                        "1C" : {
+                            "291C2C29-B663-4342-8EA1-A055E6A04D65" : {
+                                "calendar": {
+                                    "@xattrs" :
+                                    {
+                                        resourceAttr : collectionType,
+                                    },
+                                    "organizer.ics": {
+                                        "@contents" : ORGANIZER_ICS,
+                                    },
+                                    "attendee.ics": {
+                                        "@contents" : ATTENDEE_ICS,
+                                    },
+                                },
+                            },
+                        },
+                    },
+                },
+            },
+        }
+        self.createHierarchy(before, config.DocumentRoot)
+        count = (yield purgeGUID("E9E78C86-4829-4520-A35D-70DDADAB2092",
+            self.directory, self.rootResource))
+
+        # print config.DocumentRoot
+        # import pdb; pdb.set_trace()
+        self.assertEquals(count, 3)
+
+        after = {
+            "__uids__" : {
+                "E9" : {
+                    "E7" : {
+                        "E9E78C86-4829-4520-A35D-70DDADAB2092" : {
+                            "calendar": {
+                                ".db.sqlite": {
+                                    "@contents" : None, # ignore contents
+                                },
+                            },
+                        },
+                    },
+                },
+                "29" : {
+                    "1C" : {
+                        "291C2C29-B663-4342-8EA1-A055E6A04D65" : {
+                            "inbox": {
+                                ".db.sqlite": {
+                                    "@contents" : None, # ignore contents
+                                },
+                                "*.ics/UID:7ED97931-9A19-4596-9D4D-52B36D6AB803": {
+                                    "@contents" : (
+                                        "METHOD:CANCEL",
+                                        ),
+                                },
+                                "*.ics/UID:1974603C-B2C0-4623-92A0-2436DEAB07EF": {
+                                    "@contents" : (
+                                        "METHOD:REPLY",
+                                        "ATTENDEE;CN=Deprovisioned User;CUTYPE=INDIVIDUAL;PARTSTAT=DECLINED:urn:uui\r\n d:E9E78C86-4829-4520-A35D-70DDADAB2092",
+                                        ),
+                                },
+                            },
+                            "calendar": {
+                                ".db.sqlite": {
+                                    "@contents" : None, # ignore contents
+                                },
+                                "organizer.ics": {
+                                    "@contents" : (
+                                        "STATUS:CANCELLED",
+                                    ),
+                                },
+                                "attendee.ics": {
+                                    "@contents" : (
+                                        "ATTENDEE;CN=Deprovisioned User;CUTYPE=INDIVIDUAL;PARTSTAT=DECLINED;SCHEDUL\r\n E-STATUS=2.0:urn:uuid:E9E78C86-4829-4520-A35D-70DDADAB2092",
+                                        ),
+                                },
+                            },
+                        },
+                    },
+                },
+            },
+        }
+        self.assertTrue(self.verifyHierarchy(
+            os.path.join(config.DocumentRoot, "calendars"),
+            after)
+        )
+
+
+future = (datetime.utcnow() + timedelta(days=1)).strftime("%Y%m%dT%H%M%SZ")
+past = (datetime.utcnow() - timedelta(days=1)).strftime("%Y%m%dT%H%M%SZ")
+
+NON_INVITE_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+BEGIN:VEVENT
+UID:151AFC76-6036-40EF-952B-97D1840760BF
+SUMMARY:Non Invitation
+DTSTART:%s
+DURATION:PT1H
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n") % (past,)
+
+ORGANIZER_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+BEGIN:VEVENT
+UID:7ED97931-9A19-4596-9D4D-52B36D6AB803
+SUMMARY:Organizer
+DTSTART:%s
+DURATION:PT1H
+ORGANIZER:urn:uuid:E9E78C86-4829-4520-A35D-70DDADAB2092
+ATTENDEE;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:urn:uuid:E9E78C86-4829-4520-A35D-70DDADAB2092
+ATTENDEE;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:urn:uuid:291C2C29-B663-4342-8EA1-A055E6A04D65
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n") % (future,)
+
+ATTENDEE_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+BEGIN:VEVENT
+UID:1974603C-B2C0-4623-92A0-2436DEAB07EF
+SUMMARY:Attendee
+DTSTART:%s
+DURATION:PT1H
+ORGANIZER:urn:uuid:291C2C29-B663-4342-8EA1-A055E6A04D65
+ATTENDEE;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:urn:uuid:E9E78C86-4829-4520-A35D-70DDADAB2092
+ATTENDEE;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:urn:uuid:291C2C29-B663-4342-8EA1-A055E6A04D65
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n") % (future,)
+

Modified: CalendarServer/trunk/twistedcaldav/test/util.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/util.py	2010-03-26 10:27:26 UTC (rev 5407)
+++ CalendarServer/trunk/twistedcaldav/test/util.py	2010-03-27 04:09:15 UTC (rev 5408)
@@ -119,16 +119,32 @@
                     actual.remove(childName)
 
                 if childName.startswith("*"):
+                    if "/" in childName:
+                        childName, matching = childName.split("/")
+                    else:
+                        matching = False
                     ext = childName.split(".")[1]
                     found = False
                     for actualFile in actual:
                         if actualFile.endswith(ext):
-                            actual.remove(actualFile)
-                            found = True
-                            break
+                            matches = True
+                            if matching:
+                                matches = False
+                                # We want to target only the wildcard file containing
+                                # the matching string
+                                actualPath = os.path.join(parent, actualFile)
+                                with open(actualPath) as child:
+                                    contents = child.read()
+                                    if matching in contents:
+                                        matches = True
+
+                            if matches:
+                                actual.remove(actualFile)
+                                found = True
+                                break
                     if found:
-                        continue
-                    
+                        # continue
+                        childName = actualFile
 
                 childPath = os.path.join(parent, childName)
 
@@ -138,9 +154,18 @@
 
                 if childStructure.has_key("@contents"):
                     # This is a file
-                    if childStructure["@contents"] is None:
+                    expectedContents = childStructure["@contents"]
+                    if expectedContents is None:
                         # We don't care about the contents
                         pass
+                    elif isinstance(expectedContents, tuple):
+                        with open(childPath) as child:
+                            contents = child.read()
+                            for str in expectedContents:
+                                if str not in contents:
+                                    print "Contents mismatch:", childPath
+                                    print "Expecting match:\n%s\n\nActual:\n%s\n" % (str, contents)
+                                    return False
                     else:
                         with open(childPath) as child:
                             contents = child.read()
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100326/7a2713da/attachment-0001.html>


More information about the calendarserver-changes mailing list