From e396e29430bc1883b931dfdd24f9a88145bfd1d9 Mon Sep 17 00:00:00 2001 From: Francis Lachapelle Date: Fri, 13 May 2016 15:50:24 -0400 Subject: [PATCH] Fix handling of Web calendars - handle Web calendars that require authentication; - properly save the "reload on login" option; - reload Web calendars when clicking on the reload button on top of the events/tasks list; - properly activate the checkbox of new calendars (as they are enabled by default). Fixes #3326 --- NEWS | 2 + .../Appointments/SOGoWebAppointmentFolder.m | 3 +- UI/Common/English.lproj/Localizable.strings | 13 ++++ UI/Common/UIxFolderActions.m | 3 +- UI/Scheduler/UIxCalFolderActions.m | 26 +++---- UI/Scheduler/UIxCalendarProperties.m | 4 ++ UI/Templates/SchedulerUI/UIxCalMainView.wox | 47 ++++++++++++- .../js/Common/Resource.service.js | 5 +- .../js/Scheduler/Calendar.service.js | 67 +++++++++++++++++-- .../js/Scheduler/CalendarListController.js | 4 +- .../js/Scheduler/CalendarsController.js | 42 +++++++++++- 11 files changed, 188 insertions(+), 28 deletions(-) diff --git a/NEWS b/NEWS index fba0ea68f..38e18e163 100644 --- a/NEWS +++ b/NEWS @@ -20,6 +20,7 @@ Enhancements - [web] style events depending on the user participation state - [web] style transparent events (show time as free) (#3192) - [web] improved input parsing of time picker (#3659) + - [web] restored support for Web calendars that require authentication Bug fixes - [core] properly escape wide characters (#3616) @@ -53,6 +54,7 @@ Bug fixes - [web] adapted time picker to match changes of md calendar picker - [web] fixed sender addresses of draft when using multiple IMAP accounts (#3577) - [web] create a new message when clicking on a "mailto" link (#3588) + - [web] fixed handling of Web calendars option "reload on login" - [dav] we now handle the default classifications for tasks (#3541) - [eas] properly unfold long mail headers (#3152) - [eas] correctly set EAS message class for S/MIME messages (#3576) diff --git a/SoObjects/Appointments/SOGoWebAppointmentFolder.m b/SoObjects/Appointments/SOGoWebAppointmentFolder.m index 1c8d72f55..5c05d6588 100644 --- a/SoObjects/Appointments/SOGoWebAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoWebAppointmentFolder.m @@ -206,8 +206,7 @@ size_t curl_body_function(void *ptr, size_t size, size_t nmemb, void *buffer) - (BOOL) reloadOnLogin { - return [[self folderPropertyValueInCategory: @"AutoReloadedWebCalendars"] - boolValue]; + return [[self folderPropertyValueInCategory: @"AutoReloadedWebCalendars"] boolValue]; } - (NSException *) delete diff --git a/UI/Common/English.lproj/Localizable.strings b/UI/Common/English.lproj/Localizable.strings index 68d15c050..cc195d36d 100644 --- a/UI/Common/English.lproj/Localizable.strings +++ b/UI/Common/English.lproj/Localizable.strings @@ -41,6 +41,7 @@ = "Potentially anyone on the Internet will be able to access your address book \"%{0}\", even if they do not have an account on this system. Is this information suitable for the public Internet?"; "Give Access" = "Give Access"; "Keep Private" = "Keep Private"; + /* generic.js */ "Unable to subscribe to that folder!" = "Unable to subscribe to that folder!"; @@ -76,16 +77,19 @@ "45 minutes" = "45 minutes"; "1 hour" = "1 hour"; "1 day" = "1 day"; + /* common buttons */ "OK" = "OK"; "Cancel" = "Cancel"; "Yes" = "Yes"; "No" = "No"; + /* alarms */ "Reminder" = "Reminder"; "Start" = "Start"; "Due Date" = "Due Date"; "Location" = "Location"; + /* mail labels */ "Important" = "Important"; "Work" = "Work"; @@ -105,5 +109,14 @@ "No such user." = "No such user."; "You cannot (un)subscribe to a folder that you own!" = "You cannot (un)subscribe to a folder that you own!"; +/* Authentication username */ +"Username" = "Username"; + +/* Authentication password */ +"Password" = "Password"; + +/* Authentication failed */ +"Wrong username or password." = "Wrong username or password."; + /* Error message display bellow search field when the search string has less than the required number of characters */ "Enter at least %{minimumSearchLength} characters" = "Enter at least %{minimumSearchLength} characters"; \ No newline at end of file diff --git a/UI/Common/UIxFolderActions.m b/UI/Common/UIxFolderActions.m index acd43069d..422a3234c 100644 --- a/UI/Common/UIxFolderActions.m +++ b/UI/Common/UIxFolderActions.m @@ -193,8 +193,7 @@ NSString *folderName; [self _setupContext]; - folderSubscription - = [moduleSettings objectForKey: @"InactiveFolders"]; + folderSubscription = [moduleSettings objectForKey: @"InactiveFolders"]; if (!folderSubscription) { folderSubscription = [NSMutableArray array]; diff --git a/UI/Scheduler/UIxCalFolderActions.m b/UI/Scheduler/UIxCalFolderActions.m index 07d227edb..1f9682bc8 100644 --- a/UI/Scheduler/UIxCalFolderActions.m +++ b/UI/Scheduler/UIxCalFolderActions.m @@ -1,6 +1,5 @@ /* - Copyright (C) 2006-2015 Inverse inc. - Copyright (C) 2004-2005 SKYRIX Software AG + Copyright (C) 2006-2016 Inverse inc. This file is part of SOGo @@ -15,7 +14,7 @@ License for more details. You should have received a copy of the GNU Lesser General Public - License along with OGo; see the file COPYING. If not, write to the + License along with SOGo; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ @@ -34,6 +33,7 @@ #import #import +#import #import #import @@ -142,22 +142,24 @@ httpCode = 200; results = [[self clientObject] loadWebCalendar]; - if ([results objectForKey: @"error"]) - httpCode = 500; + if ([results objectForKey: @"status"]) + httpCode = [[results objectForKey: @"status"] intValue]; return [self responseWithStatus: httpCode andJSONRepresentation: results]; } - (WOResponse *) setCredentialsAction { + NSDictionary *params; + NSString *username, *password; WORequest *request; WOResponse *response; - NSString *username, *password; request = [context request]; + params = [[request contentAsString] objectFromJSONString]; - username = [[request formValueForKey: @"username"] stringByTrimmingSpaces]; - password = [[request formValueForKey: @"password"] stringByTrimmingSpaces]; + username = [[params objectForKey: @"username"] stringByTrimmingSpaces]; + password = [[params objectForKey: @"password"] stringByTrimmingSpaces]; if ([username length] > 0 && [password length] > 0) { [[self clientObject] setUsername: username @@ -165,10 +167,10 @@ response = [self responseWith204]; } else - response - = (WOResponse *) [NSException exceptionWithHTTPStatus: 400 - reason: @"missing 'username' and/or" - @" 'password' parameters"]; + response = [self responseWithStatus: 400 + andJSONRepresentation: [NSDictionary dictionaryWithObject: @"missing 'username' and/or" + @" 'password' parameters" + forKey: @"message"]]; return response; } diff --git a/UI/Scheduler/UIxCalendarProperties.m b/UI/Scheduler/UIxCalendarProperties.m index a4dadd947..43d168fe2 100644 --- a/UI/Scheduler/UIxCalendarProperties.m +++ b/UI/Scheduler/UIxCalendarProperties.m @@ -107,6 +107,10 @@ if ([o isKindOfClass: [NSNumber class]]) [calendar setSynchronize: [o boolValue]]; + o = [params objectForKey: @"reloadOnLogin"]; + if ([o isKindOfClass: [NSNumber class]] && [calendar isKindOfClass: [SOGoWebAppointmentFolder class]]) + [(SOGoWebAppointmentFolder *) calendar setReloadOnLogin: [o boolValue]]; + values = [params objectForKey: @"notifications"]; if ([values isKindOfClass: [NSDictionary class]]) { diff --git a/UI/Templates/SchedulerUI/UIxCalMainView.wox b/UI/Templates/SchedulerUI/UIxCalMainView.wox index 8b929a823..49acdeb25 100644 --- a/UI/Templates/SchedulerUI/UIxCalMainView.wox +++ b/UI/Templates/SchedulerUI/UIxCalMainView.wox @@ -195,6 +195,11 @@ + + + + + @@ -227,7 +232,13 @@ ng-true-value="1" ng-false-value="0" label:aria-label="Enable"> -

{{calendar.name}}

+

+ + warning + {{ calendar.$error }} + + {{calendar.name}} +

+ @@ -724,4 +736,37 @@ + + + diff --git a/UI/WebServerResources/js/Common/Resource.service.js b/UI/WebServerResources/js/Common/Resource.service.js index e96afb2a1..2a26a398d 100644 --- a/UI/WebServerResources/js/Common/Resource.service.js +++ b/UI/WebServerResources/js/Common/Resource.service.js @@ -77,10 +77,7 @@ }) .then(function(response) { return deferred.resolve(response.data); - }, function(response) { - if (response.status == 404) - return deferred.reject(); - }); + }, deferred.reject); return deferred.promise; }; diff --git a/UI/WebServerResources/js/Scheduler/Calendar.service.js b/UI/WebServerResources/js/Scheduler/Calendar.service.js index d5983e2e7..de517b841 100644 --- a/UI/WebServerResources/js/Scheduler/Calendar.service.js +++ b/UI/WebServerResources/js/Scheduler/Calendar.service.js @@ -231,20 +231,48 @@ urls: { webCalendarURL: url } }); var calendar = new Calendar(calendarData); - Calendar.$add(calendar); Calendar.$$resource.fetch(calendar.id, 'reload').then(function(data) { // TODO: show a toast of the reload status Calendar.$log.debug(JSON.stringify(data, undefined, 2)); + Calendar.$add(calendar); + d.resolve(); + }, function(response) { + if (response.status == 401) { + // Web calendar requires authentication + d.resolve(calendar); + } + else { + d.reject(); + } }); - d.resolve(); - }, function() { - d.reject(); - }); + }, d.reject); } return d.promise; }; + /** + * @function reloadWebCalendars + * @memberof Calendar + * @desc Reload all Web calendars + * @return a promise combining the results of all HTTP operations + */ + Calendar.reloadWebCalendars = function() { + var promises = []; + + _.forEach(this.$webcalendars, function(calendar) { + var promise = Calendar.$$resource.fetch(calendar.id, 'reload'); + promise.then(function(data) { + calendar.$error = false; + }, function(response) { + calendar.$error = l(response.statusText); + }); + promises.push(promise); + }); + + return Calendar.$q.all(promises); + }; + /** * @function $deleteComponents * @memberof Calendar @@ -292,6 +320,7 @@ */ Calendar.prototype.init = function(data) { this.color = this.color || '#AAAAAA'; + this.active = 1; angular.extend(this, data); if (this.id) { this.$acl = new Calendar.$$Acl('Calendar/' + this.id); @@ -438,6 +467,34 @@ }); }; + /** + * @function setCredentials + * @memberof Calendar.prototype + * @desc Set the credentials for a Web calendar that requires authentication + * @returns a promise of the HTTP operation + */ + Calendar.prototype.setCredentials = function(username, password) { + var _this = this, + d = Calendar.$q.defer(); + + Calendar.$$resource.post(this.id, 'set-credentials', { username: username, password: password }).then(function() { + Calendar.$$resource.fetch(_this.id, 'reload').then(function(data) { + Calendar.$add(_this); + d.resolve(); + }, function(response) { + if (response.status == 401) { + // Authentication failed + d.reject(l('Wrong username or password')); + } + else { + d.reject(response.statusText); + } + }); + }, d.reject); + + return d.promise; + }; + /** * @function export * @memberof Calendar.prototype diff --git a/UI/WebServerResources/js/Scheduler/CalendarListController.js b/UI/WebServerResources/js/Scheduler/CalendarListController.js index 0b18bab74..538b89617 100644 --- a/UI/WebServerResources/js/Scheduler/CalendarListController.js +++ b/UI/WebServerResources/js/Scheduler/CalendarListController.js @@ -309,7 +309,9 @@ } function reload() { - $rootScope.$emit('calendars:list'); + Calendar.reloadWebCalendars().finally(function() { + $rootScope.$emit('calendars:list'); + }); } function cancelSearch() { diff --git a/UI/WebServerResources/js/Scheduler/CalendarsController.js b/UI/WebServerResources/js/Scheduler/CalendarsController.js index ab0f27620..ea9cb745f 100644 --- a/UI/WebServerResources/js/Scheduler/CalendarsController.js +++ b/UI/WebServerResources/js/Scheduler/CalendarsController.js @@ -127,8 +127,48 @@ function addWebCalendar() { Dialog.prompt(l('Subscribe to a web calendar...'), l('URL of the Calendar'), {inputType: 'url'}) .then(function(url) { - Calendar.$addWebCalendar(url); + Calendar.$addWebCalendar(url).then(function(calendar) { + if (angular.isObject(calendar)) { + // Web calendar requires HTTP authentication + $mdDialog.show({ + parent: angular.element(document.body), + clickOutsideToClose: true, + escapeToClose: true, + templateUrl: 'UIxWebCalendarAuthDialog', + controller: WebCalendarAuthDialogController, + controllerAs: '$WebCalendarAuthDialogController', + locals: { + url: url, + calendar: calendar + } + }); + } + }); }); + + /** + * @ngInject + */ + WebCalendarAuthDialogController.$inject = ['scope', '$mdDialog', 'url', 'calendar']; + function WebCalendarAuthDialogController(scope, $mdDialog, url, calendar) { + var vm = this, + parts = url.split("/"), + hostname = parts[2]; + + vm.title = l("Please identify yourself to %{0}").formatted(hostname); + vm.authenticate = function(form) { + if (form.$valid || !form.$error.required) { + calendar.setCredentials(vm.username, vm.password).then(function(message) { + $mdDialog.hide(); + }, function(reason) { + form.password.$setValidity('credentials', false); + }); + } + }; + vm.cancel = function() { + $mdDialog.cancel(); + }; + } } function confirmDelete(folder) {