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