diff --git a/Apache/SOGo.conf b/Apache/SOGo.conf
index 7921d91bd..1762ebac3 100644
--- a/Apache/SOGo.conf
+++ b/Apache/SOGo.conf
@@ -9,6 +9,13 @@ AliasMatch /SOGo/so/ControlPanel/Products/(.*)/Resources/(.*) \
AllowOverride None
Order deny,allow
Allow from all
+
+ # Explicitly allow caching of static content to avoid browser specific behavior.
+ # A resource's URL MUST change in order to have the client load the new version.
+
+ ExpiresActive On
+ ExpiresDefault "access plus 1 year"
+
@@ -64,4 +71,4 @@ ProxyPass /SOGo http://127.0.0.1:20000/SOGo retry=0
# The remote address will appear in SOGo's log files and in the X-Forward
# header of emails.
RewriteEngine On
-RewriteRule ^/SOGo/(.*)$ /SOGo/$1 [env=REMOTE_HOST:%{REMOTE_ADDR},PT]
\ No newline at end of file
+RewriteRule ^/SOGo/(.*)$ /SOGo/$1 [env=REMOTE_HOST:%{REMOTE_ADDR},PT]
diff --git a/ChangeLog b/ChangeLog
index 00f83764c..ffc062ee7 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,75 @@
+2012-05-24 Jean Raby
+
+ * debian*/rules: Restart sogod after pkg upgrade (dh_installinit -R)
+ * Scripts/sogo-init.d-*: add support for conditional restart.
+ Patch from Romain Le Disez
+ * sogo.spec: Restart sogod during post installation if it was already running
+
+ * Apache/SOGo.conf:
+ Use mod_expires to allow long term caching of static content. (1 year)
+ Note that from now on, a resource's URL _must_ change to let the client
+ reload it. This is now done automatically for 'rsrc' in the wox templates,
+ but must be done manually for files referenced from css and js.
+
+2012-05-23 Wolfgang Sourdeau
+
+ * UI/WebServerResources/generic.js
+ (clickEventWrapper.button_clickEventWrappe): don't invoke
+ "preventDefault" on elements that do not have a tagName of "A".
+
+2012-05-18 Jean Raby
+
+ * SoObjects/Mailer/SOGoDraftObject.m (bodyPartForAttachmentWithName):
+ Merge back lost code to handle encoding of binary and rcf822 attachments.
+
+2012-05-15 Wolfgang Sourdeau
+
+ * OpenChange/gen-property-selectors.py: the use of the "{}" to
+ construct a set is only valid on py2.7 therefore we make use of
+ set([]) instead.
+
+2012-05-15 Jean Raby
+
+ * SoObjects/Appointments/SOGoAppointmentObject.m (_handleResourcesConflicts):
+ Deny access to resources if the resource's ACL don't allow the organizer
+ to read its freebusy info.
+ Without this, sogo would always auto-accept invitations from 'unprivileged'
+ users, potentially bypassing the multiplebooking parameter.
+
+2012-05-14 Wolfgang Sourdeau
+
+ * OpenChange/gen-property-selectors.py: "bannedProps" is now a
+ set, for faster lookups.
+
+ * UI/WebServerResources/UIxContactsUserFolders.js: folder ids are
+ in the form "user:module/folder", therefore we must remove the
+ first char, which is a slash.
+
+ * UI/WebServerResources/generic.js (subscribeToFolder): prepend a
+ "/" to the folder name, as it comes originally in the form
+ user:module/folder.
+
+ * UI/WebServerResources/UIxPreferences.js (initMailAccounts)
+ (displayMailAccount): getElementsByTagName seems too standard for
+ IE, we use $$() now so that we can invoke "each" on the result set
+ without triggering an exception.
+
+2012-05-09 Jean Raby
+
+ * SoObjects/Appointments/SOGoAppointmentObject.m (PUTAction):
+ Delete bitrotten code that could end up duplicating attendees.
+ Behavior exposed by the new caldav tests
+
+ * Tests/Integration/config.py.in
+ * Tests/Integration/test-caldav-scheduling.py
+ * Tests/Integration/test-ical.py
+ * Tests/Integration/test-davacl.py:
+ Use an unprivileged webdavclient where possible.
+ This would have uncovered the resources calendar autocreation bug.
+
+ * Tests/Integration/test-caldav-scheduling.py:
+ New tests to excercise somewhat fragile code in dav autoscheduling.
+
2012-05-09 Jean Raby
* SoObject/SOGo/SOGoParentFolder.m (_createPersonalFolder):
diff --git a/Documentation/SOGo Installation Guide.odt b/Documentation/SOGo Installation Guide.odt
index c9f49e63a..a26e451af 100644
Binary files a/Documentation/SOGo Installation Guide.odt and b/Documentation/SOGo Installation Guide.odt differ
diff --git a/Documentation/SOGo Native Microsoft Outlook Configuration.odt b/Documentation/SOGo Native Microsoft Outlook Configuration.odt
index 5a5b9af37..8b3802886 100644
Binary files a/Documentation/SOGo Native Microsoft Outlook Configuration.odt and b/Documentation/SOGo Native Microsoft Outlook Configuration.odt differ
diff --git a/NEWS b/NEWS
index 6969883f1..199458107 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,4 @@
-1.3.15 (2012-05-DD)
+1.3.15 (2012-05-15)
-------------------
New Features
- sources address books are now exposed in Apple and iOS AddressBook app
@@ -9,6 +9,7 @@ New Features
calculator
- new domain default (SOGoMailCustomFromEnabled) to allow users to change
their "from" and "reply-to" headers
+ - access to external calendar subscriptions (.ics) with authentication
- new domain default (SOGoHideSystemEMail) to hide or not the system
email. This is currently limited to CalDAV operations
@@ -27,6 +28,9 @@ Bug Fixes
- when saving a draft, fixed content-transfer-encoding to properly handle
8bit data
- escaped single-quote in HTML view of contacts
+ - fixed support of recurrent events with Apple iCal
+ - fixed overbooking handling of resources with recurrent events
+ - fixed auto-accept of resources when added later to an event
1.3.14 (2012-03-23)
-------------------
diff --git a/OpenChange/gen-property-selectors.py b/OpenChange/gen-property-selectors.py
index 0a496db39..8c728bb99 100755
--- a/OpenChange/gen-property-selectors.py
+++ b/OpenChange/gen-property-selectors.py
@@ -106,32 +106,37 @@ extern const enum MAPITAGS MAPIStoreSupportedProperties[];
# hack: some properties have multiple and incompatible types. Sometimes those
# props are not related at all...
-bannedProps = [ "PidTagBodyHtml", "PidTagFavAutosubfolders",
- "PidTagAttachDataObj", "PidTagAclTable", "PidTagAclData",
- "PidTagRulesTable", "PidTagRulesData", "PidTagDisableWinsock",
- "PidTagHierarchyServer", "PidTagOfflineAddrbookEntryid",
- "PidTagShorttermEntryidFromObject",
- "PidTagNormalMessageSizeExtended",
- "PidTagAssocMessageSizeExtended", "PidTagMessageSizeExtended",
- "PidTagOabContainerGuid",
- "PidTagOfflineAddressBookMessageClass", "PidTagScriptData",
- "PidTagOfflineAddressBookTruncatedProperties",
- "PidTagOfflineAddressBookContainerGuid",
- "PidTagOfflineAddressBookDistinguishedName",
- "PidTagOfflineAddressBookShaHash",
- "PidTagSenderTelephoneNumber", "PidTagGatewayNeedsToRefresh",
- "PidTagWlinkType", "PidTagWlinkFlags",
- "PidTagWlinkGroupClsid", "PidTagWlinkGroupName",
- "PidTagWlinkGroupHeaderID",
- "PidTagScheduleInfoDelegatorWantsCopy", "PidTagWlinkOrdinal",
- "PidTagWlinkSection", "PidTagWlinkCalendarColor",
- "PidTagWlinkAddressBookEID", "PidTagWlinkFolderType",
- "PidTagScheduleInfoDelegateNames",
- "PidTagScheduleInfoDelegateEntryIds",
- "PidTagBusiness2TelephoneNumbers",
- "PidTagHome2TelephoneNumbers",
- "PidTagAttachDataObject", "PidTagShorttermEntryIdFromObject",
- ]
+bannedProps = set(["PidTagBodyHtml", "PidTagFavAutosubfolders",
+ "PidTagAttachDataObj", "PidTagAclTable", "PidTagAclData",
+ "PidTagRulesTable", "PidTagRulesData",
+ "PidTagDisableWinsock",
+ "PidTagHierarchyServer", "PidTagOfflineAddrbookEntryid",
+ "PidTagShorttermEntryidFromObject",
+ "PidTagNormalMessageSizeExtended",
+ "PidTagAssocMessageSizeExtended",
+ "PidTagMessageSizeExtended",
+ "PidTagOabContainerGuid",
+ "PidTagOfflineAddressBookMessageClass", "PidTagScriptData",
+ "PidTagOfflineAddressBookTruncatedProperties",
+ "PidTagOfflineAddressBookContainerGuid",
+ "PidTagOfflineAddressBookDistinguishedName",
+ "PidTagOfflineAddressBookShaHash",
+ "PidTagSenderTelephoneNumber",
+ "PidTagGatewayNeedsToRefresh",
+ "PidTagWlinkType", "PidTagWlinkFlags",
+ "PidTagWlinkGroupClsid", "PidTagWlinkGroupName",
+ "PidTagWlinkGroupHeaderID",
+ "PidTagScheduleInfoDelegatorWantsCopy",
+ "PidTagWlinkOrdinal",
+ "PidTagWlinkSection", "PidTagWlinkCalendarColor",
+ "PidTagWlinkAddressBookEID", "PidTagWlinkFolderType",
+ "PidTagScheduleInfoDelegateNames",
+ "PidTagScheduleInfoDelegateEntryIds",
+ "PidTagBusiness2TelephoneNumbers",
+ "PidTagHome2TelephoneNumbers",
+ "PidTagAttachDataObject",
+ "PidTagShorttermEntryIdFromObject",
+ ])
def ParseExchangeH(names, lines):
state = 0
@@ -199,7 +204,7 @@ def FindHFile(filename):
return found
def ProcessHeaders(names, hdict):
- for filename in hdict.keys():
+ for filename in hdict:
header_filename = FindHFile(filename)
header_file = open(header_filename, "r")
lines = header_file.readlines()
@@ -228,8 +233,8 @@ if __name__ == "__main__":
names = {}
ProcessHeaders(names,
- { "gen_ndr/exchange.h": ParseExchangeH,
- "mapistore/mapistore_nameid.h": ParseMapistoreNameIDH })
+ {"gen_ndr/exchange.h": ParseExchangeH,
+ "mapistore/mapistore_nameid.h": ParseMapistoreNameIDH})
getters = []
getters_idx = []
@@ -243,12 +248,10 @@ if __name__ == "__main__":
prop_types = {}
# sanitization: only take unicode version of text properties
- all_keys = names.keys()
- for name in all_keys:
- prop_tag = names[name]
+ for name, prop_tag in names.iteritems():
prop_id = prop_tag >> 16
prop_type = prop_tag & 0xffff
- if not prop_types.has_key(prop_id):
+ if not prop_id in prop_types:
prop_types[prop_id] = []
prop_types[prop_id].append(prop_type)
if (prop_type & 0xfff) == 0x001e:
@@ -256,19 +259,15 @@ if __name__ == "__main__":
names[name] = prop_tag
#sanitization: report multiple types for the same keynames
- all_keys = prop_types.keys()
- for prop_id in all_keys:
- xtypes = prop_types[prop_id]
+ for prop_id, xtypes in prop_types.iteritems():
cnt = len(xtypes)
if cnt > 1:
print "%d types available for prop id 0x%.4x: %s" % (cnt, prop_id, ", ".join(["%.4x" % x for x in xtypes]))
supported_properties = []
- all_keys = names.keys()
current_getter_idx = 0
highest_prop_idx = 0
- for name in all_keys:
- prop_tag = names[name]
+ for name, prop_tag in names.iteritems():
supported_properties.append(" 0x%.8x" % prop_tag);
prop_idx = (prop_tag & 0xffff0000) >> 16
getters_idx[prop_idx] = " 0x%.4x" % current_getter_idx
@@ -285,14 +284,14 @@ if __name__ == "__main__":
filename = "%s.m" % output
h_filename = "%s.h" % output
outf = open(filename, "wb+")
- outf.write(m_template % { "getters_idx": ",\n".join(getters_idx),
- "getters": ",\n".join(getters),
- "nbr_getters": len(getters),
- "last_property": highest_prop_idx,
- "nbr_supported_properties": len(supported_properties),
- "supported_properties": ",\n".join(supported_properties),
- "filename": filename,
- "h_filename": h_filename })
+ outf.write(m_template % {"getters_idx": ",\n".join(getters_idx),
+ "getters": ",\n".join(getters),
+ "nbr_getters": len(getters),
+ "last_property": highest_prop_idx,
+ "nbr_supported_properties": len(supported_properties),
+ "supported_properties": ",\n".join(supported_properties),
+ "filename": filename,
+ "h_filename": h_filename})
outf.close()
outf = open(h_filename, "wb+")
@@ -301,7 +300,7 @@ if __name__ == "__main__":
if ord(x) < 65 or ord(x) > 90:
x = "_"
exclusion = exclusion + x
- outf.write(h_template % { "prototypes": "\n".join(prototypes),
- "h_exclusion": exclusion,
- "filename": h_filename })
+ outf.write(h_template % {"prototypes": "\n".join(prototypes),
+ "h_exclusion": exclusion,
+ "filename": h_filename })
outf.close()
diff --git a/Scripts/sogo-init.d-redhat b/Scripts/sogo-init.d-redhat
index db5b4cfba..76fea17f5 100755
--- a/Scripts/sogo-init.d-redhat
+++ b/Scripts/sogo-init.d-redhat
@@ -146,12 +146,17 @@ case "$1" in
restart)
restart
;;
+ condrestart|try-restart)
+ if status -p "$PIDFILE" $DAEMON >&/dev/null; then
+ restart
+ fi
+ ;;
status)
status -p "$PIDFILE" $DAEMON
;;
*)
N=/etc/init.d/$NAME
- echo "Usage: $N {start|stop|restart|status}" >&2
+ echo "Usage: $N {start|stop|restart|condrestart|status}" >&2
exit 1
;;
esac
diff --git a/Scripts/sogo-init.d-sles b/Scripts/sogo-init.d-sles
index ad44c0685..d4f3afbc1 100755
--- a/Scripts/sogo-init.d-sles
+++ b/Scripts/sogo-init.d-sles
@@ -96,6 +96,11 @@ case "$1" in
startproc -u $USER $DAEMON $DAEMON_OPTS || true
echo "$NAME."
;;
+ condrestart|try-restart)
+ if checkproc -p "$PIDFILE" $DAEMON >&/dev/null; then
+ restart
+ fi
+ ;;
status)
checkproc -p $PIDFILE $DAEMON
result="$?"
@@ -115,7 +120,7 @@ case "$1" in
fi
;;
*)
- echo "Usage: $NAME {start|stop|restart|status}" >&2
+ echo "Usage: $NAME {start|stop|restart|condrestart|status}" >&2
exit 1
;;
esac
diff --git a/SoObjects/Appointments/English.lproj/Localizable.strings b/SoObjects/Appointments/English.lproj/Localizable.strings
index a963ca29a..6de15e75b 100644
--- a/SoObjects/Appointments/English.lproj/Localizable.strings
+++ b/SoObjects/Appointments/English.lproj/Localizable.strings
@@ -67,4 +67,5 @@ vtodo_class2 = "(Confidential task)";
= "%{Attendee} %{SentByText}has not yet decided upon your event invitation.";
/* Resources */
-"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}.";
\ No newline at end of file
+"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Cannot access resource: \"%{Cn} %{SystemEmail}\"";
+"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}.";
diff --git a/SoObjects/Appointments/French.lproj/Localizable.strings b/SoObjects/Appointments/French.lproj/Localizable.strings
index 49baa8aa8..5171403ee 100644
--- a/SoObjects/Appointments/French.lproj/Localizable.strings
+++ b/SoObjects/Appointments/French.lproj/Localizable.strings
@@ -67,4 +67,5 @@ vtodo_class2 = "(Tâche confidentielle)";
= "%{Attendee} %{SentByText}choisit de reporter sa décision par rapport à votre invitation.";
/* Resources */
-"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\"." = "Le nombre maximum (%{NumberOfSimultaneousBookings}) de réservation(s) simultanée(s) a été atteint pour la ressource \"%{Cn} %{SystemEmail}\".";
\ No newline at end of file
+"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Impossible d'accéder à la resource: \"%{Cn} %{SystemEmail}\"";
+"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\"." = "Le nombre maximum (%{NumberOfSimultaneousBookings}) de réservation(s) simultanée(s) a été atteint pour la ressource \"%{Cn} %{SystemEmail}\".";
diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m
index 50f02bc98..596e2d2ba 100644
--- a/SoObjects/Appointments/SOGoAppointmentObject.m
+++ b/SoObjects/Appointments/SOGoAppointmentObject.m
@@ -467,7 +467,19 @@
folder = [[SOGoUser userWithLogin: currentUID]
personalCalendarFolderInContext: context];
-
+ // Deny access to the resource if the ACLs don't allow the user
+ if (![folder aclSQLListingFilter])
+ {
+ NSDictionary *values;
+ NSString *reason;
+
+ values = [NSDictionary dictionaryWithObjectsAndKeys:
+ [user cn], @"Cn",
+ [user systemEmail], @"SystemEmail"];
+ reason = [values keysWithFormat: [self labelForKey: @"Cannot access resource: \"%{Cn} %{SystemEmail}\""]];
+ return [NSException exceptionWithHTTPStatus:403 reason: reason];
+ }
+
fbInfo = [NSMutableArray arrayWithArray: [folder fetchFreeBusyInfosFrom: start
to: end]];
@@ -1891,16 +1903,6 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// one from the request.
[rq setContent: [[[newEvent parent] versitString] dataUsingEncoding: [rq contentEncoding]]];
}
-
- // A RECURRENCE-ID was removed so there has to be a change in the master event
- // We could also have an EXDATE added in the master component of the attendees
- // so we always compare the MASTER event.
- if (!master)
- {
- newEvent = [newEvents objectAtIndex: 0];
- oldEvent = [oldEvents objectAtIndex: 0];
- [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent];
- }
}
//
// else => attendee is responding
diff --git a/SoObjects/Mailer/SOGoDraftObject.m b/SoObjects/Mailer/SOGoDraftObject.m
index 3d0c540ee..e1770d0dc 100644
--- a/SoObjects/Mailer/SOGoDraftObject.m
+++ b/SoObjects/Mailer/SOGoDraftObject.m
@@ -1119,7 +1119,7 @@ static NSString *userAgent = nil;
NGMimeBodyPart *bodyPart;
NSString *s;
NSData *content;
- BOOL attachAsString;
+ BOOL attachAsString, attachAsRFC822;
NSString *p;
id body;
@@ -1134,6 +1134,7 @@ static NSString *userAgent = nil;
return nil;
}
attachAsString = NO;
+ attachAsRFC822 = NO;
/* prepare header of body part */
@@ -1143,6 +1144,8 @@ static NSString *userAgent = nil;
[map setObject:s forKey: @"content-type"];
if ([s hasPrefix: @"text/plain"] || [s hasPrefix: @"text/html"])
attachAsString = YES;
+ else if ([s hasPrefix: @"message/rfc822"])
+ attachAsRFC822 = YES;
}
if ((s = [self contentDispositionForAttachmentWithName:_name]))
{
@@ -1181,6 +1184,19 @@ static NSString *userAgent = nil;
content = [[NSData alloc] initWithContentsOfMappedFile:p];
[content autorelease];
+ if (attachAsRFC822)
+ {
+ [map setObject: @"8bit" forKey: @"content-transfer-encoding"];
+ [map setObject: @"inline" forKey: @"content-disposition"];
+ }
+ else
+ {
+ content = [content dataByEncodingBase64];
+ [map setObject: @"base64" forKey: @"content-transfer-encoding"];
+ }
+ [map setObject:[NSNumber numberWithInt:[content length]]
+ forKey: @"content-length"];
+
/* Note: the -init method will create a temporary file! */
body = [[NGMimeFileData alloc] initWithBytes:[content bytes]
length:[content length]];
diff --git a/Tests/Integration/config.py.in b/Tests/Integration/config.py.in
index 149dff65d..0de6d4a4f 100644
--- a/Tests/Integration/config.py.in
+++ b/Tests/Integration/config.py.in
@@ -1,16 +1,24 @@
-# setup: username must be super-user or have read-access to PUBLIC events in
-# both attendee and delegate's personal calendar
+# setup: 4 user are needed: username, superuser, attendee1, attendee1_delegate
+# superuser must be a sogo superuser...
hostname = "localhost"
port = "80"
username = "myuser"
password = "mypass"
+superuser = "super"
+superuser_password="pass"
+
subscriber_username = "otheruser"
subscriber_password = "otherpass"
attendee1 = "user@domain.com"
-attendee1_delegate = "otheruser@domain.com"
+attendee1_username = "user"
+attendee1_password = "pass"
+
+attendee1_delegate = "user2@domain.com"
+attendee1_delegate_username = "sogo2"
+attendee1_delegate_password = "sogo"
resource_no_overbook = "res"
resource_can_overbook = "res-nolimit"
diff --git a/Tests/Integration/test-caldav-scheduling.py b/Tests/Integration/test-caldav-scheduling.py
index 559bd9753..4e6a4a268 100755
--- a/Tests/Integration/test-caldav-scheduling.py
+++ b/Tests/Integration/test-caldav-scheduling.py
@@ -1,10 +1,15 @@
#!/usr/bin/python
-# setup: username must be super-user or have read-access to PUBLIC events in
-# both attendee and delegate's personal calendar
+# setup: 4 users are needed: username, attendee1_username,
+# attendee1_delegate_username and superuser.
+# when writing new tests, avoid using superuser when not absolutely needed
from config import hostname, port, username, password, \
- attendee1, attendee1_delegate, \
+ superuser, superuser_password, \
+ attendee1, attendee1_username, \
+ attendee1_password, \
+ attendee1_delegate, attendee1_delegate_username, \
+ attendee1_delegate_password, \
resource_no_overbook, resource_can_overbook
import datetime
@@ -98,10 +103,17 @@ class CalDAVPropertiesTest(unittest.TestCase):
% (proppatch.response["status"],
proppatch.response["body"]))
-class CalDAVITIPDelegationTest(unittest.TestCase):
+class CalDAVSchedulingTest(unittest.TestCase):
def setUp(self):
+ self.superuser_client = webdavlib.WebDAVClient(hostname, port,
+ superuser, superuser_password)
self.client = webdavlib.WebDAVClient(hostname, port,
username, password)
+ self.attendee1_client = webdavlib.WebDAVClient(hostname, port,
+ attendee1_username, attendee1_password)
+ self.attendee1_delegate_client = webdavlib.WebDAVClient(hostname, port,
+ attendee1_delegate_username, attendee1_password)
+
utility = utilities.TestUtility(self, self.client)
(self.user_name, self.user_email) = utility.fetchUserInfo(username)
(self.attendee1_name, self.attendee1_email) = utility.fetchUserInfo(attendee1)
@@ -113,18 +125,24 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
self.attendee1_calendar = "/SOGo/dav/%s/Calendar/personal/" % attendee1
self.attendee1_delegate_calendar = "/SOGo/dav/%s/Calendar/personal/" % attendee1_delegate
+ # fetch non existing event to let sogo create the calendars in the db
+ self._getEvent(self.client, "%snonexistent" % self.user_calendar, exp_status=404)
+ self._getEvent(self.attendee1_client, "%snonexistent" % self.attendee1_calendar, exp_status=404)
+ self._getEvent(self.attendee1_delegate_client, "%snonexistent" %
+ self.attendee1_delegate_calendar, exp_status=404)
+
def tearDown(self):
self._deleteEvent(self.client,
"%stest-delegation.ics" % self.user_calendar, None)
- self._deleteEvent(self.client,
+ self._deleteEvent(self.attendee1_client,
"%stest-delegation.ics" % self.attendee1_calendar, None)
- self._deleteEvent(self.client,
+ self._deleteEvent(self.attendee1_delegate_client,
"%stest-delegation.ics" % self.attendee1_delegate_calendar,
None)
self._deleteEvent(self.client,
"%stest-add-attendee.ics" % self.user_calendar, None)
- self._deleteEvent(self.client,
+ self._deleteEvent(self.attendee1_client,
"%stest-add-attendee.ics" % self.attendee1_calendar, None)
self._deleteEvent(self.client,
"%stest-no-overbook.ics" % self.user_calendar, None)
@@ -135,9 +153,13 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
self._deleteEvent(self.client,
"%stest-can-overbook-overlap.ics" % self.user_calendar, None)
self._deleteEvent(self.client,
- "%stest-remove-attendee.ics" % self.user_calendar, None)
+ "%stest-rrule-exception-invitation-dance.ics" % self.user_calendar, None)
+ self._deleteEvent(self.attendee1_client,
+ "%stest-rrule-exception-invitation-dance.ics" % self.attendee1_calendar, None)
self._deleteEvent(self.client,
- "%stest-remove-attendee-no-org.ics" % self.user_calendar, None)
+ "%stest-rrule-invitation-deleted-exdate-dance.ics" % self.user_calendar, None)
+ self._deleteEvent(self.attendee1_client,
+ "%stest-rrule-invitation-deleted-exdate-dance.ics" % self.attendee1_calendar, None)
def _newEvent(self, summary="test event", uid="test", transp=0):
transparency = ("OPAQUE", "TRANSPARENT")
@@ -156,7 +178,7 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
vevent.add('dtstamp').value = now
vevent.add('last-modified').value = now
vevent.add('created').value = now
-
+ vevent.add('class').value = "PUBLIC"
vevent.add('sequence').value = "0"
return newCal
@@ -242,6 +264,41 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
% (email,
compared_attendees[email], attendees[email]))
+ def testAddAttendee(self):
+ """ add attendee after event creation """
+
+ # make sure the event doesn't exist
+ ics_name = "test-add-attendee.ics"
+ self._deleteEvent(self.client,
+ "%s%s" % (self.user_calendar,ics_name), None)
+ self._deleteEvent(self.attendee1_client,
+ "%s%s" % (self.attendee1_calendar,ics_name), None)
+
+ # 1. create an event in the organiser's calendar
+ event = self._newEvent(summary="Test add attendee", uid="Test add attendee")
+ organizer = event.vevent.add('organizer')
+ organizer.cn_param = self.user_name
+ organizer.value = self.user_email
+ self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
+
+ # 2. add an attendee
+ event.add("method").value = "REQUEST"
+ attendee = event.vevent.add('attendee')
+ attendee.cn_param = self.attendee1_name
+ attendee.rsvp_param = "TRUE"
+ attendee.partstat_param = "NEEDS-ACTION"
+ attendee.value = self.attendee1_email
+ self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event,
+ exp_status=204)
+
+
+ # 3. verify that the attendee has the event
+ attendee_event = self._getEvent(self.attendee1_client, "%s%s" % (self.attendee1_calendar, ics_name))
+
+ # 4. make sure the received event match the original one
+ # XXX is this enough?
+ self.assertEquals(event.vevent.uid, attendee_event.vevent.uid)
+
def testUninviteAttendee(self):
""" Remove attendee after event creation """
@@ -249,7 +306,7 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
ics_name = "test-remove-attendee.ics"
self._deleteEvent(self.client,
"%s%s" % (self.user_calendar,ics_name), None)
- self._deleteEvent(self.client,
+ self._deleteEvent(self.attendee1_client,
"%s%s" % (self.attendee1_calendar,ics_name), None)
# 1. create an event in the organiser's calendar
@@ -275,7 +332,7 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
exp_status=204)
# 3. verify that the attendee has the event
- attendee_event = self._getEvent(self.client, "%s%s" % (self.attendee1_calendar, ics_name))
+ attendee_event = self._getEvent(self.attendee1_client, "%s%s" % (self.attendee1_calendar, ics_name))
# 4. make sure the received event match the original one
self.assertEquals(event.vevent.uid, attendee_event.vevent.uid)
@@ -287,92 +344,42 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
exp_status=204)
# 6. verify that the attendee doesn't have the event anymore
- attendee_event = self._getEvent(self.client, "%s%s" % (self.attendee1_calendar, ics_name), 404)
+ attendee_event = self._getEvent(self.attendee1_client, "%s%s" % (self.attendee1_calendar, ics_name), 404)
- def testUninviteAttendeeNoOrganizer(self):
- """ Remove attendee and organizer after event creation """
+ def testAddAttendee(self):
+ """ add attendee after event creation """
# make sure the event doesn't exist
- ics_name = "test-remove-attendee-no-org.ics"
+ ics_name = "test-add-attendee.ics"
self._deleteEvent(self.client,
"%s%s" % (self.user_calendar,ics_name), None)
self._deleteEvent(self.client,
"%s%s" % (self.attendee1_calendar,ics_name), None)
# 1. create an event in the organiser's calendar
- event = self._newEvent(summary="Test uninvite attendee no org", uid="Test uninvite attendee no org")
- # keep a copy around for updates without other attributes
- plainEvent = vobject.iCalendar()
- plainEvent.copy(event)
-
+ event = self._newEvent(summary="Test add attendee", uid="Test add attendee")
organizer = event.vevent.add('organizer')
organizer.cn_param = self.user_name
organizer.value = self.user_email
-
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
-
# 2. add an attendee
- event.add("method").value = "REQUEST"
attendee = event.vevent.add('attendee')
attendee.cn_param = self.attendee1_name
attendee.rsvp_param = "TRUE"
attendee.partstat_param = "NEEDS-ACTION"
attendee.value = self.attendee1_email
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event,
- exp_status=204)
+ exp_status=204)
+
# 3. verify that the attendee has the event
- attendee_event = self._getEvent(self.client, "%s%s" % (self.attendee1_calendar, ics_name))
-
+ attendee_event = self._getEvent(self.attendee1_client, "%s%s" % (self.attendee1_calendar, ics_name))
+
# 4. make sure the received event match the original one
+ # XXX is this enough?
self.assertEquals(event.vevent.uid, attendee_event.vevent.uid)
- # 5. put the event back without attendee or organizer
- now = datetime.datetime.now(dateutil.tz.gettz("America/Montreal"))
- plainEvent.vevent.last_modified.value = now
- self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), plainEvent,
- exp_status=204)
-
- # 6. verify that the attendee doesn't have the event anymore
- attendee_event = self._getEvent(self.client, "%s%s" % (self.attendee1_calendar, ics_name), 404)
-
-
- def testAddAttendee(self):
- """ add attendee after event creation """
-
- # make sure the event doesn't exist
- ics_name = "test-add-attendee.ics"
- self._deleteEvent(self.client,
- "%s%s" % (self.user_calendar,ics_name), None)
- self._deleteEvent(self.client,
- "%s%s" % (self.attendee1_calendar,ics_name), None)
-
- # 1. create an event in the organiser's calendar
- event = self._newEvent(summary="Test add attendee", uid="Test add attendee")
- organizer = event.vevent.add('organizer')
- organizer.cn_param = self.user_name
- organizer.value = self.user_email
- self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
-
- # 2. add an attendee
- event.add("method").value = "REQUEST"
- attendee = event.vevent.add('attendee')
- attendee.cn_param = self.attendee1_name
- attendee.rsvp_param = "TRUE"
- attendee.partstat_param = "NEEDS-ACTION"
- attendee.value = self.attendee1_email
- self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event,
- exp_status=204)
-
-
- # 3. verify that the attendee has the event
- attendee_event = self._getEvent(self.client, "%s%s" % (self.attendee1_calendar, ics_name))
-
- # 4. make sure the received event match the original one
- # XXX is this enough?
- self.assertEquals(event.vevent.uid, attendee_event.vevent.uid)
-
def testResourceNoOverbook(self):
""" try to overbook a resource """
@@ -450,15 +457,221 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
self._putEvent(self.client, "%s%s" % (self.user_calendar, ob_ics_name), event)
+ def testRruleExceptionInvitationDance(self):
+ """ RRULE exception invitation dance """
+
+ # This workflow is based on what lightning 1.2.1 does
+ # create a reccurring event
+ # add an exception
+ # invite bob to the exception:
+ # bob is declined in the master event
+ # bob needs-action in the exception
+ # bob accepts
+ # bob is declined in the master event
+ # bob is accepted in the exception
+ # the organizer 'uninvites' bob
+ # the event disappears from bob's calendar
+ # bob isn't in the master+exception event
+
+ ics_name = "test-rrule-exception-invitation-dance.ics"
+ self._deleteEvent(self.client,
+ "%s%s" % (self.user_calendar, ics_name), None)
+ self._deleteEvent(self.attendee1_client,
+ "%s%s" % (self.attendee1_calendar, ics_name), None)
+
+ # 1. create a recurring event in the organiser's calendar
+ summary="Test reccuring exception invite cancel"
+ uid="Test-recurring-exception-invite-cancel"
+ event = self._newEvent(summary, uid)
+ event.vevent.add('rrule').value = "FREQ=DAILY;COUNT=5"
+
+ self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
+
+ # read the event back from the server
+ org_ev = self._getEvent(self.client, "%s%s" % (self.user_calendar, ics_name))
+
+ # 2. Add an exception to the master event and invite attendee1 to it
+ now = datetime.datetime.now(dateutil.tz.gettz("America/Montreal"))
+ org_ev.vevent.last_modified.value = now
+ orig_dtstart = org_ev.vevent.dtstart.value
+ orig_dtend = org_ev.vevent.dtend.value
+
+ ev_exception = org_ev.add("vevent")
+ ev_exception.add('created').value = now
+ ev_exception.add('last-modified').value = now
+ ev_exception.add('dtstamp').value = now
+ ev_exception.add('uid').value = uid
+ ev_exception.add('summary').value = summary
+ # out of laziness, add the exception for the first occurence of the event
+ recurrence_id = orig_dtstart
+ ev_exception.add('recurrence-id').value = recurrence_id
+
+ ev_exception.add('transp').value = "OPAQUE"
+ ev_exception.add('description').value = "Exception"
+ ev_exception.add('sequence').value = "1"
+ ev_exception.add('dtstart').value = orig_dtstart
+ ev_exception.add('dtend').value = orig_dtend
+
+ # 2.1 Add attendee1 and organizer to the exception
+ organizer = ev_exception.add('organizer')
+ organizer.cn_param = self.user_name
+ organizer.partstat_param = "ACCEPTED"
+ organizer.value = self.user_email
+ attendee = ev_exception.add('attendee')
+ attendee.cn_param = self.attendee1_name
+ attendee.rsvp_param = "TRUE"
+ attendee.role_param = "REQ-PARTICIPANT"
+ attendee.partstat_param = "NEEDS-ACTION"
+ attendee.value = self.attendee1_email
+
+ self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), org_ev,
+ exp_status=204)
+
+ # 3. Make sure the attendee got the event
+ attendee_ev = self._getEvent(self.attendee1_client, "%s%s" % (self.attendee1_calendar, ics_name))
+
+ for ev in attendee_ev.vevent_list:
+ try:
+ if (ev.recurrence_id.value):
+ attendee_ev_exception = ev
+ except:
+ attendee_ev_master = ev
+
+ # make sure sogo doesn't duplicate attendees - yes, we've seen that
+ self.assertEquals(len(attendee_ev_master.attendee_list), 1)
+ self.assertEquals(len(attendee_ev_exception.attendee_list), 1)
+
+ # 4. The master event must contain the invitation, declined
+ self.assertEquals(attendee_ev_master.attendee.partstat_param, "DECLINED")
+
+ # 5. The exception event contain the invitation, NEEDS-ACTION
+ self.assertEquals(attendee_ev_exception.attendee.partstat_param, "NEEDS-ACTION")
+
+ # 6. attendee accepts invitation
+ attendee_ev_exception.attendee.partstat_param = "ACCEPTED"
+ self._putEvent(self.attendee1_client, "%s%s" % (self.attendee1_calendar, ics_name),
+ attendee_ev, exp_status=204)
+
+ # fetch the organizer's event
+ org_ev = self._getEvent(self.client, "%s%s" % (self.user_calendar, ics_name))
+ for ev in org_ev.vevent_list:
+ try:
+ if (ev.recurrence_id.value):
+ org_ev_exception = ev
+ except:
+ org_ev_master = ev
+
+ # make sure sogo doesn't duplicate attendees
+ self.assertEquals(len(org_ev_master.attendee_list), 1)
+ self.assertEquals(len(org_ev_exception.attendee_list), 1)
+
+ # 7. Make sure organizer got the accept for the exception and
+ # that the attendee is still declined in the master
+ self.assertEquals(org_ev_exception.attendee.partstat_param, "ACCEPTED")
+ self.assertEquals(org_ev_master.attendee.partstat_param, "DECLINED")
+
+ # 8. delete the attendee from the master event (uninvite)
+ # The event should be deleted from the attendee's calendar
+ del org_ev_exception.attendee
+ self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name),
+ org_ev, exp_status=204)
+ del org_ev_master.attendee
+ self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name),
+ org_ev, exp_status=204)
+
+ self._getEvent(self.client, "%s%s" % (self.attendee1_calendar, ics_name),
+ exp_status=404)
+
+ # now be happy
+
+ def testRruleInvitationDeleteExdate(self):
+ """RRULE invitation delete exdate dance"""
+
+ # Workflow:
+ # Create an recurring event and invite Bob
+ # Add an exdate to the master event
+ # Verify that the exdate has propagated to Bob's calendar
+ # Add an exdate to bob's version of the event
+ # Verify that an exception has been created in the org's calendar
+ # and that bob is 'declined'
+
+ ics_name = "test-rrule-invitation-deleted-exdate-dance.ics"
+ self._deleteEvent(self.client,
+ "%s%s" % (self.user_calendar, ics_name), None)
+ self._deleteEvent(self.attendee1_client,
+ "%s%s" % (self.attendee1_calendar, ics_name), None)
+
+ # 1. create a recurring event in the organiser's calendar
+ summary="Test-rrule-invitation-deleted-exdate-dance"
+ uid=summary
+ event = self._newEvent(summary, uid)
+ event.vevent.add('rrule').value = "FREQ=DAILY;COUNT=5"
+ organizer = event.vevent.add('organizer')
+ organizer.cn_param = self.user_name
+ organizer.partstat_param = "ACCEPTED"
+ organizer.value = self.user_email
+ attendee = event.vevent.add('attendee')
+ attendee.cn_param = self.attendee1_name
+ attendee.rsvp_param = "TRUE"
+ attendee.role_param = "REQ-PARTICIPANT"
+ attendee.partstat_param = "NEEDS-ACTION"
+ attendee.value = self.attendee1_email
+
+ self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
+
+ # 2. Make sure the attendee got it
+ self._getEvent(self.attendee1_client, "%s%s" % (self.attendee1_calendar, ics_name))
+
+ # 3. Add exdate to master event
+ org_ev=self._getEvent(self.client, "%s%s" % (self.user_calendar, ics_name))
+ orig_dtstart = org_ev.vevent.dtstart.value
+ # exdate is a list in vobject.icalendar
+ org_exdate = [orig_dtstart.astimezone(dateutil.tz.gettz("UTC"))]
+ org_ev.vevent.add('exdate').value = org_exdate
+ self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), org_ev, exp_status=204)
+
+ # 4. make sure the attendee has the exdate
+ attendee_ev = self._getEvent(self.attendee1_client, "%s%s" %
+ (self.attendee1_calendar, ics_name))
+ self.assertEqual(org_exdate, attendee_ev.vevent.exdate.value)
+
+ # 5. Create an exdate in the attendee's calendar
+ new_exdate = orig_dtstart + datetime.timedelta(days=2)
+ attendee_exdate = [new_exdate.astimezone(dateutil.tz.gettz("UTC"))]
+ attendee_ev.vevent.add('exdate').value = attendee_exdate
+ now = datetime.datetime.now(dateutil.tz.gettz("America/Montreal"))
+ attendee_ev.vevent.last_modified.value = now
+ self._putEvent(self.attendee1_client, "%s%s" % (self.attendee1_calendar, ics_name),
+ attendee_ev, exp_status=204)
+
+ # 6. Make sure the attendee is:
+ # needs-action in master event
+ # declined in the new exception created by the exdate above
+ org_ev=self._getEvent(self.client, "%s%s" % (self.user_calendar, ics_name))
+ for ev in org_ev.vevent_list:
+ try:
+ if (ev.recurrence_id.value == attendee_exdate[0]):
+ org_ev_exception = ev
+ except:
+ org_ev_master = ev
+
+ self.assertTrue(org_ev_exception)
+ # make sure sogo doesn't duplicate attendees
+ self.assertEquals(len(org_ev_master.attendee_list), 1)
+ self.assertEquals(len(org_ev_exception.attendee_list), 1)
+
+ self.assertEqual(org_ev_master.attendee.partstat_param, "NEEDS-ACTION");
+ self.assertEqual(org_ev_exception.attendee.partstat_param, "DECLINED");
+
def testInvitationDelegation(self):
""" invitation delegation """
# the invitation must not exist
self._deleteEvent(self.client,
"%stest-delegation.ics" % self.user_calendar, None)
- self._deleteEvent(self.client,
+ self._deleteEvent(self.attendee1_client,
"%stest-delegation.ics" % self.attendee1_calendar, None)
- self._deleteEvent(self.client,
+ self._deleteEvent(self.attendee1_delegate_client,
"%stest-delegation.ics" % self.attendee1_delegate_calendar,
None)
@@ -482,7 +695,7 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
"%stest-delegation.ics" % self.user_calendar,
invitation)
- att_inv = self._getEvent(self.client,
+ att_inv = self._getEvent(self.attendee1_client,
"%stest-delegation.ics"
% self.attendee1_calendar)
self._compareAttendees(att_inv, invitation)
@@ -502,19 +715,19 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
delegate.partstat_param = "NEEDS-ACTION"
delegate.value = self.attendee1_delegate_email
- self._postEvent(self.client,
+ self._postEvent(self.attendee1_client,
self.attendee1_calendar, invitation,
self.attendee1_email, [self.attendee1_delegate_email])
invitation.method.value = "REPLY"
- self._postEvent(self.client,
+ self._postEvent(self.attendee1_client,
self.attendee1_calendar, invitation,
self.attendee1_email, [self.user_email])
del invitation.method
- self._putEvent(self.client,
+ self._putEvent(self.attendee1_client,
"%stest-delegation.ics" % self.attendee1_calendar,
invitation, 204)
- del_inv = self._getEvent(self.client,
+ del_inv = self._getEvent(self.attendee1_delegate_client,
"%stest-delegation.ics"
% self.attendee1_delegate_calendar)
self._compareAttendees(del_inv, invitation)
@@ -528,18 +741,18 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
invitation.add("method").value = "REPLY"
delegate.partstat_param = "ACCEPTED"
- self._postEvent(self.client,
+ self._postEvent(self.attendee1_delegate_client,
self.attendee1_delegate_calendar, invitation,
self.attendee1_delegate_email, [self.user_email, self.attendee1_email])
del invitation.method
- self._putEvent(self.client,
+ self._putEvent(self.attendee1_delegate_client,
"%stest-delegation.ics" % self.attendee1_delegate_calendar,
invitation, 204)
org_inv = self._getEvent(self.client,
"%stest-delegation.ics" % self.user_calendar)
self._compareAttendees(org_inv, invitation)
- att_inv = self._getEvent(self.client,
+ att_inv = self._getEvent(self.attendee1_client,
"%stest-delegation.ics" % self.attendee1_calendar)
self._compareAttendees(att_inv, invitation)
@@ -551,7 +764,7 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
cancellation.copy(invitation)
cancellation.add("method").value = "CANCEL"
cancellation.vevent.sequence.value = "1"
- self._postEvent(self.client,
+ self._postEvent(self.attendee1_client,
self.attendee1_calendar, cancellation,
self.attendee1_email, [self.attendee1_delegate_email])
@@ -560,12 +773,12 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
del attendee1.delegated_to_param
invitation.add("method").value = "REPLY"
invitation.vevent.remove(delegate)
- self._postEvent(self.client,
+ self._postEvent(self.attendee1_client,
self.attendee1_calendar, invitation,
self.attendee1_email, [self.user_email])
del invitation.method
- self._putEvent(self.client,
+ self._putEvent(self.attendee1_client,
"%stest-delegation.ics" % self.attendee1_calendar,
invitation, 204)
@@ -573,7 +786,7 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
"%stest-delegation.ics" % self.user_calendar)
self._compareAttendees(org_inv, invitation)
- del_inv = self._getEvent(self.client,
+ del_inv = self._getEvent(self.attendee1_delegate_client,
"%stest-delegation.ics" % self.attendee1_delegate_calendar, 404)
# 5. org updates inv.
@@ -595,7 +808,7 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
"%stest-delegation.ics" % self.user_calendar,
invitation, 204)
- att_inv = self._getEvent(self.client,
+ att_inv = self._getEvent(self.attendee1_client,
"%stest-delegation.ics" % self.attendee1_calendar)
self._compareAttendees(att_inv, invitation)
@@ -613,22 +826,22 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
delegate.partstat_param = "NEEDS-ACTION"
delegate.value = self.attendee1_delegate_email
- self._postEvent(self.client,
+ self._postEvent(self.attendee1_client,
self.attendee1_calendar, invitation,
self.attendee1_email, [self.attendee1_delegate_email])
invitation.method.value = "REPLY"
- self._postEvent(self.client,
+ self._postEvent(self.attendee1_client,
self.attendee1_calendar, invitation,
self.attendee1_email, [self.user_email])
del invitation.method
- self._putEvent(self.client,
+ self._putEvent(self.attendee1_client,
"%stest-delegation.ics" % self.attendee1_calendar,
invitation, 204)
org_inv = self._getEvent(self.client,
"%stest-delegation.ics" % self.user_calendar)
self._compareAttendees(org_inv, invitation)
- del_inv = self._getEvent(self.client,
+ del_inv = self._getEvent(self.attendee1_delegate_client,
"%stest-delegation.ics"
% self.attendee1_delegate_calendar)
self._compareAttendees(del_inv, invitation)
@@ -638,19 +851,19 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
invitation.add("method").value = "REPLY"
delegate.partstat_param = "ACCEPTED"
- self._postEvent(self.client,
+ self._postEvent(self.attendee1_delegate_client,
self.attendee1_delegate_calendar, invitation,
self.attendee1_delegate_email, [self.user_email,
self.attendee1_email])
del invitation.method
- self._putEvent(self.client,
+ self._putEvent(self.attendee1_delegate_client,
"%stest-delegation.ics" % self.attendee1_delegate_calendar,
invitation, 204)
org_inv = self._getEvent(self.client,
"%stest-delegation.ics" % self.user_calendar)
self._compareAttendees(org_inv, invitation)
- att_inv = self._getEvent(self.client,
+ att_inv = self._getEvent(self.attendee1_client,
"%stest-delegation.ics" % self.attendee1_calendar)
self._compareAttendees(att_inv, invitation)
@@ -674,10 +887,10 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
"%stest-delegation.ics" % self.user_calendar,
invitation, 204)
- att_inv = self._getEvent(self.client,
+ att_inv = self._getEvent(self.attendee1_client,
"%stest-delegation.ics" % self.attendee1_calendar)
self._compareAttendees(att_inv, invitation)
- del_inv = self._getEvent(self.client,
+ del_inv = self._getEvent(self.attendee1_client,
"%stest-delegation.ics" % self.attendee1_calendar)
self._compareAttendees(del_inv, invitation)
@@ -702,9 +915,9 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
"%stest-delegation.ics" % self.user_calendar,
invitation, 204)
- att_inv = self._getEvent(self.client,
+ att_inv = self._getEvent(self.attendee1_client,
"%stest-delegation.ics" % self.attendee1_calendar, 404)
- del_inv = self._getEvent(self.client,
+ del_inv = self._getEvent(self.attendee1_delegate_client,
"%stest-delegation.ics" % self.attendee1_delegate_calendar, 404)
if __name__ == "__main__":
diff --git a/Tests/Integration/test-davacl.py b/Tests/Integration/test-davacl.py
index d1fddc6a0..0a8ecd787 100755
--- a/Tests/Integration/test-davacl.py
+++ b/Tests/Integration/test-davacl.py
@@ -1,6 +1,7 @@
#!/usr/bin/python
-from config import hostname, port, username, password, subscriber_username, subscriber_password
+from config import hostname, port, username, password, subscriber_username, subscriber_password, \
+ superuser, superuser_password
import sys
import unittest
@@ -25,7 +26,7 @@ import utilities
class DAVCalendarSuperUserAclTest(unittest.TestCase):
def __init__(self, arg):
self.client = webdavlib.WebDAVClient(hostname, port,
- username, password)
+ superuser, superuser_password)
self.resource = "/SOGo/dav/%s/Calendar/test-dav-superuser-acl/" % subscriber_username
self.filename = "suevent.ics"
self.url = "%s%s" % (self.resource, self.filename)
@@ -949,6 +950,8 @@ class DAVPublicAccessTest(unittest.TestCase):
class DAVCalendarPublicAclTest(unittest.TestCase):
def setUp(self):
self.createdRsrc = None
+ self.superuser_client = webdavlib.WebDAVClient(hostname, port,
+ superuser, superuser_password)
self.client = webdavlib.WebDAVClient(hostname, port,
username, password)
self.subscriber_client = webdavlib.WebDAVClient(hostname, port,
@@ -959,7 +962,7 @@ class DAVCalendarPublicAclTest(unittest.TestCase):
def tearDown(self):
if self.createdRsrc is not None:
delete = webdavlib.WebDAVDELETE(self.createdRsrc)
- self.client.execute(delete)
+ self.superuser_client.execute(delete)
def testCollectionAccessNormalUser(self):
"""normal user access to (non-)shared resource from su"""
@@ -1091,7 +1094,7 @@ class DAVCalendarPublicAclTest(unittest.TestCase):
for rsrc in [ 'personal', 'test-dav-acl' ]:
resource = '%s%s/' % (parentColl, rsrc)
mkcol = webdavlib.WebDAVMKCOL(resource)
- self.client.execute(mkcol)
+ self.superuser_client.execute(mkcol)
acl_utility = utilities.TestCalendarACLUtility(self,
self.subscriber_client,
resource)
diff --git a/Tests/Integration/test-ical.py b/Tests/Integration/test-ical.py
index 8c9c76871..6f128de7c 100755
--- a/Tests/Integration/test-ical.py
+++ b/Tests/Integration/test-ical.py
@@ -1,6 +1,9 @@
#!/usr/bin/python
-from config import hostname, port, username, password, subscriber_username
+# FIXME: we should avoid using superuser if possible
+
+from config import hostname, port, username, password, subscriber_username, \
+ superuser, superuser_password
import unittest
import sogotests
@@ -48,7 +51,7 @@ class iCalTest(unittest.TestCase):
for x in members ]
props = { "{DAV:}group-member-set": membersHref }
proppatch = webdavlib.WebDAVPROPPATCH(resource, props)
- client = webdavlib.WebDAVClient(hostname, port, username, password)
+ client = webdavlib.WebDAVClient(hostname, port, superuser, superuser_password)
client.user_agent = "DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374); Mac OS X/10.6.2 (10C540)"
client.execute(proppatch)
self.assertEquals(proppatch.response["status"], 207,
@@ -60,7 +63,7 @@ class iCalTest(unittest.TestCase):
resource = '/SOGo/dav/%s/' % user
propfind = webdavlib.WebDAVPROPFIND(resource,
["{DAV:}group-membership"], 0)
- client = webdavlib.WebDAVClient(hostname, port, username, password)
+ client = webdavlib.WebDAVClient(hostname, port, superuser, superuser_password)
client.user_agent = "DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374); Mac OS X/10.6.2 (10C540)"
client.execute(propfind)
@@ -73,7 +76,7 @@ class iCalTest(unittest.TestCase):
resource = '/SOGo/dav/%s/' % user
prop = "{http://calendarserver.org/ns/}calendar-proxy-%s-for" % perm
propfind = webdavlib.WebDAVPROPFIND(resource, [prop], 0)
- client = webdavlib.WebDAVClient(hostname, port, username, password)
+ client = webdavlib.WebDAVClient(hostname, port, superuser, superuser_password)
client.user_agent = "DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374); Mac OS X/10.6.2 (10C540)"
client.execute(propfind)
@@ -136,7 +139,7 @@ class iCalTest(unittest.TestCase):
def testCalendarProxy2(self):
"""calendar-proxy as used from SOGo"""
- client = webdavlib.WebDAVClient(hostname, port, username, password)
+ client = webdavlib.WebDAVClient(hostname, port, superuser, superuser_password)
client.user_agent = "DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374); Mac OS X/10.6.2 (10C540)"
personal_resource = "/SOGo/dav/%s/Calendar/personal/" % username
dav_utility = utilities.TestCalendarACLUtility(self,
diff --git a/UI/WebServerResources/ContactsUI.js b/UI/WebServerResources/ContactsUI.js
index 12250965c..6a14b5678 100644
--- a/UI/WebServerResources/ContactsUI.js
+++ b/UI/WebServerResources/ContactsUI.js
@@ -1509,45 +1509,45 @@ function currentFolderIsRemote() {
return rc;
}
-function startDragging(e, itm) {
- var row = e.target;
- var handle = $("dragDropVisual");
+function startDragging(event, ui) {
+ var row = event.target;
+ var handle = ui.helper;
var contacts = $('contactsList').getSelectedRowsId();
var count = contacts.length;
if (count == 0 || contacts.indexOf(row.id) < 0) {
- onRowClick(e, $(row.id));
+ onRowClick(event, $(row.id));
contacts = $("contactsList").getSelectedRowsId();
count = contacts.length;
}
- handle.update(count);
+ handle.html(count);
- if (e.shiftKey || currentFolderIsRemote()) {
- handle.addClassName("copy");
+ if (event.shiftKey || currentFolderIsRemote()) {
+ handle.addClass("copy");
}
handle.show();
}
-function whileDragging(e, itm) {
- if (e) {
- var handle = $("dragDropVisual");
- if (e.shiftKey || currentFolderIsRemote())
- handle.addClassName ("copy");
- else if (handle.hasClassName ("copy"))
- handle.removeClassName ("copy");
+function whileDragging(event, ui) {
+ if (event) {
+ var handle = ui.helper;
+ if (event.shiftKey || currentFolderIsRemote())
+ handle.addClass("copy");
+ else if (handle.hasClass("copy"))
+ handle.removeClass("copy");
}
}
-function stopDragging(e, itm) {
- var handle = $("dragDropVisual");
+function stopDragging(event, ui) {
+ var handle = ui.helper;
handle.hide();
- if (handle.hasClassName("copy"))
- handle.removeClassName("copy");
+ if (handle.hasClass("copy"))
+ handle.removeClass("copy");
}
function dropAction(event, ui) {
var action = "move";
- if ($("dragDropVisual").hasClassName("copy"))
+ if (ui.helper.hasClass("copy"))
action = "copy";
else
$('contactView').update();
diff --git a/UI/WebServerResources/MailerUI.js b/UI/WebServerResources/MailerUI.js
index be49e0ee7..3422fee67 100644
--- a/UI/WebServerResources/MailerUI.js
+++ b/UI/WebServerResources/MailerUI.js
@@ -2932,7 +2932,7 @@ function configureDroppables() {
drop: dropAction });
}
-function startDragging(e, ui) {
+function startDragging(event, ui) {
var handle = ui.helper;
var count = $('messageListBody').getSelectedRowsId().length;
@@ -2942,29 +2942,29 @@ function startDragging(e, ui) {
}
handle.html(count);
- if (e.shiftKey) {
- handle.addClassName("copy");
+ if (event.shiftKey) {
+ handle.addClass("copy");
}
handle.show();
}
-function whileDragging(e, ui) {
- if (e) {
- var handle = $("dragDropVisual");
- if (e.shiftKey)
- handle.addClassName("copy");
- else if (handle.hasClassName("copy"))
- handle.removeClassName("copy");
+function whileDragging(event, ui) {
+ if (event) {
+ var handle = ui.helper;
+ if (event.shiftKey)
+ handle.addClass("copy");
+ else if (handle.hasClass("copy"))
+ handle.removeClass("copy");
}
}
-function stopDragging() {
- var handle = $("dragDropVisual");
+function stopDragging(event, ui) {
+ var handle = ui.helper;
handle.hide();
- if (handle.hasClassName("copy"))
- handle.removeClassName("copy");
+ if (handle.hasClass("copy"))
+ handle.removeClass("copy");
for (var i = 0; i < accounts.length; i++) {
- handle.removeClassName("account" + i);
+ handle.removeClass("account" + i);
}
}
@@ -2975,7 +2975,7 @@ function dropAction(event, ui) {
var destAct = destination.getAttribute("dataname").split("/")[1];
if (sourceAct == destAct) {
var f;
- if ($("dragDropVisual").hasClassName("copy")) {
+ if (ui.helper.hasClass("copy")) {
// Message(s) copied
f = onMailboxMenuCopy.bind(destination);
}
diff --git a/UI/WebServerResources/SchedulerUI.js b/UI/WebServerResources/SchedulerUI.js
index d3a6048ba..601279356 100644
--- a/UI/WebServerResources/SchedulerUI.js
+++ b/UI/WebServerResources/SchedulerUI.js
@@ -1,5 +1,3 @@
-/* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-
var listFilter = 'view_today';
var listOfSelection = null;
diff --git a/UI/WebServerResources/UIxContactsUserFolders.js b/UI/WebServerResources/UIxContactsUserFolders.js
index adc1f8541..34df71132 100644
--- a/UI/WebServerResources/UIxContactsUserFolders.js
+++ b/UI/WebServerResources/UIxContactsUserFolders.js
@@ -1,5 +1,3 @@
-/* -*- Mode: java; tab-width: 2; c-label-minimum-indentation: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-
var d;
function onSearchFormSubmit() {
@@ -182,7 +180,7 @@ function addFolderBranchToTree(tree, user, folder, nodeId, subId, isLast) {
icon += 'tb-mail-addressbook-flat-16x16.png';
else
icon += 'calendar-folder-16x16.png';
- var folderId = user + ":" + folderInfos[1];
+ var folderId = user + ":" + folderInfos[1].substr(1);
var name = folderInfos[0]; // name has the format "Folername (Firstname Lastname )"
var pos = name.lastIndexOf(' (');
if (pos > -1)
diff --git a/UI/WebServerResources/UIxPreferences.js b/UI/WebServerResources/UIxPreferences.js
index e0ea18bd7..27c76912c 100644
--- a/UI/WebServerResources/UIxPreferences.js
+++ b/UI/WebServerResources/UIxPreferences.js
@@ -469,26 +469,23 @@ function initMailAccounts() {
}
}
- var info = $("accountInfo");
- var inputs = info.getElementsByTagName("input");
+ var inputs = $$("#accountInfo input");
for (var i = 0; i < inputs.length; i++) {
$(inputs[i]).observe("change", onMailAccountInfoChange);
}
- info = $("identityInfo");
- inputs = info.getElementsByTagName("input");
+ inputs = $$("#identityInfo input");
for (var i = 0; i < inputs.length; i++) {
$(inputs[i]).observe("change", onMailIdentityInfoChange);
}
$("actSignature").observe("click", onMailIdentitySignatureClick);
displayMailAccount(mailAccounts[0], true);
- info = $("returnReceiptsInfo");
- inputs = info.getElementsByTagName("input");
+ inputs = $$("#returnReceiptsInfo input");
for (var i = 0; i < inputs.length; i++) {
$(inputs[i]).observe("change", onMailReceiptInfoChange);
}
- inputs = info.getElementsByTagName("select");
+ inputs = $$("#returnReceiptsInfo select");
for (var i = 0; i < inputs.length; i++) {
$(inputs[i]).observe("change", onMailReceiptActionChange);
}
@@ -656,13 +653,11 @@ function onMailAccountEntryClick(event) {
}
function displayMailAccount(mailAccount, readOnly) {
- var fieldSet = $("accountInfo");
- var inputs = $(fieldSet.getElementsByTagName("input"));
+ var inputs = $$("#accountInfo input");
inputs.each(function (i) { i.disabled = readOnly;
i.mailAccount = mailAccount; });
- fieldSet = $("identityInfo");
- inputs = $(fieldSet.getElementsByTagName("input"));
+ inputs = $$("#identityInfo input");
inputs.each(function (i) { i.mailAccount = mailAccount; });
if (!mailCustomFromEnabled) {
for (var i = 0; i < 2; i++) {
diff --git a/UI/WebServerResources/generic.js b/UI/WebServerResources/generic.js
index aa232bbad..6313c8924 100644
--- a/UI/WebServerResources/generic.js
+++ b/UI/WebServerResources/generic.js
@@ -47,9 +47,17 @@ var removeFolderRequestCount = 0;
// Email validation regexp
var emailRE = /^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i;
+
+/* This function enables the execution of a wrapper function just before the
+ user callback is executed. The wrapper in question executes "preventDefault"
+ to the event parameter if and only when "this" is a link. The goal of this
+ operation is to prevent links with attached even handlers to be followed,
+ including those with an href set to "#". */
function clickEventWrapper(functionRef) {
function button_clickEventWrapper(event) {
- preventDefault(event);
+ if (this.tagName == "A") {
+ preventDefault(event);
+ }
return functionRef.apply(this, [event]);
}
@@ -1211,7 +1219,7 @@ function subscribeToFolder(refreshCallback, refreshCallbackData) {
var folderPath = folderData[1];
if (username != UserLogin) {
var url = (UserFolderURL + "../" + username
- + folderPath + "/subscribe");
+ + "/" + folderPath + "/subscribe");
if (document.subscriptionAjaxRequest) {
document.subscriptionAjaxRequest.aborted = true;
document.subscriptionAjaxRequest.abort();
@@ -1266,10 +1274,10 @@ function accessToSubscribedFolder(serverFolder) {
var username = parts[0];
var paths = parts[1].split("/");
if (username == UserLogin) {
- folder = paths[2];
+ folder = paths[1];
}
else {
- folder = "/" + username.asCSSIdentifier() + "_" + paths[2];
+ folder = "/" + username.asCSSIdentifier() + "_" + paths[1];
}
}
else {
@@ -2047,7 +2055,7 @@ function _showPromptDialog(title, label, callback, defaultValue) {
document.body.appendChild(dialog);
dialogs[title+label] = dialog;
}
- jQuery(dialog).fadeIn('fast', function () { dialog.down("input").focus(); });
+ jQuery(dialog).fadeIn('fast', function () { dialog.down("input").focus(); });
}
function showSelectDialog(title, label, options, button, callbackFcn, callbackArg, defaultValue) {
@@ -2098,14 +2106,10 @@ function _showSelectDialog(title, label, options, button, callbackFcn, callbackA
function showAuthenticationDialog(label, callback) {
var div = $("bgDialogDiv");
- if (div && div.visible() && div.getOpacity() > 0) {
- log("push");
+ if (div && div.visible() && div.getOpacity() > 0)
dialogsStack.push(_showAuthenticationDialog.bind(this, label, callback));
- }
- else {
- log("show");
+ else
_showAuthenticationDialog(label, callback);
- }
}
function _showAuthenticationDialog(label, callback) {
diff --git a/debian-multiarch/rules b/debian-multiarch/rules
index 1580a1ce5..d66269b96 100755
--- a/debian-multiarch/rules
+++ b/debian-multiarch/rules
@@ -85,7 +85,7 @@ install-arch: build-arch
binary-arch: build-arch install-arch
dh_testdir
dh_testroot
- dh_installinit -r
+ dh_installinit -R
dh_installlogrotate
dh_installcron
dh_installchangelogs ChangeLog
diff --git a/debian/rules b/debian/rules
index 023869853..cfa64de7b 100755
--- a/debian/rules
+++ b/debian/rules
@@ -84,7 +84,7 @@ install-arch: build-arch
binary-arch: build-arch install-arch
dh_testdir
dh_testroot
- dh_installinit -r
+ dh_installinit -R
dh_installlogrotate
dh_installcron
dh_installchangelogs ChangeLog
diff --git a/sogo.spec b/sogo.spec
index 8be88354e..bab4cf4ea 100644
--- a/sogo.spec
+++ b/sogo.spec
@@ -292,6 +292,7 @@ if ! id sogo >& /dev/null; then /usr/sbin/adduser sogo > /dev/null 2>&1; fi
# update timestamp on imgs,css,js to let apache know the files changed
find %{_libdir}/GNUstep/SOGo/WebServerResources -exec touch {} \;
/sbin/chkconfig --add sogod
+/etc/init.d/sogod condrestart >&/dev/null
%preun
if [ "$1" == "0" ]
@@ -311,6 +312,9 @@ fi
# ********************************* changelog *************************
%changelog
+* Fri May 24 2012 Jean Raby
+- %post: restart sogo if it was running before rpm install
+
* Fri Mar 16 2012 Jean Raby
- %post: update timestamp on imgs,css,js to let apache know the files changed