From ff5b25972f80f841616c4055139869ad914df128 Mon Sep 17 00:00:00 2001 From: Alexandre Cloutier Date: Wed, 8 Oct 2014 13:44:37 -0400 Subject: [PATCH] GUI for mobile version of AclUsers Conflicts: UI/Common/UIxAclEditor.m UI/Common/UIxUserRightsEditor.m UI/WebServerResources/js/Common/acl-model.js --- UI/Common/UIxAclEditor.h | 2 +- UI/Common/UIxAclEditor.m | 20 +- UI/Common/UIxUserRightsEditor.m | 22 +- UI/MainUI/SOGoUserHomePage.m | 14 +- .../ContactsUI/UIxContactFoldersView.wox | 53 ++++- UI/WebServerResources/js/Common/acl-model.js | 59 +++++ UI/WebServerResources/js/mobile/ContactsUI.js | 215 +++++++++++++++++- 7 files changed, 364 insertions(+), 21 deletions(-) create mode 100644 UI/WebServerResources/js/Common/acl-model.js diff --git a/UI/Common/UIxAclEditor.h b/UI/Common/UIxAclEditor.h index 515877bb9..e249ec756 100644 --- a/UI/Common/UIxAclEditor.h +++ b/UI/Common/UIxAclEditor.h @@ -31,7 +31,7 @@ BOOL publishInFreeBusy; NSArray *aclUsers; NSArray *savedUIDs; - NSMutableArray *users; + NSMutableDictionary *users; NSString *currentUser; NSString *defaultUserID; } diff --git a/UI/Common/UIxAclEditor.m b/UI/Common/UIxAclEditor.m index 8c39a7ebd..373adf331 100644 --- a/UI/Common/UIxAclEditor.m +++ b/UI/Common/UIxAclEditor.m @@ -46,7 +46,7 @@ aclUsers = nil; prepared = NO; publishInFreeBusy = NO; - users = [NSMutableArray new]; + users = [NSMutableDictionary new]; currentUser = nil; defaultUserID = nil; savedUIDs = nil; @@ -96,9 +96,23 @@ { if (!([currentUID isEqualToString: ownerLogin] || [currentUID isEqualToString: defaultUserID] - || [currentUID isEqualToString: @"anonymous"])) - [users addObjectUniquely: currentUID]; + || [currentUID isEqualToString: @"anonymous"])) + { + // Set the current user in order to get information associated with it + [self setCurrentUser: currentUID]; + + // Build the object associated with the key; currentUID + object = [NSDictionary dictionaryWithObjectsAndKeys: currentUser, @"uid", + [self currentUserClass], @"userClass", + [self currentUserDisplayName], @"displayName", + [NSNumber numberWithBool:[self currentUserIsSubscribed]], @"isSubscribed", nil]; + [users setObject:object forKey: currentUID]; + } } + // Adding the Any authenticated user and the public access + [users setObject:[NSDictionary dictionaryWithObjectsAndKeys: @"", @"uid", [self labelForKey: @"Any Authenticated User"], @"displayName", @"public-user", @"userClass", nil] forKey: @""]; + if ([self isPublicAccessEnabled]) + [users setObject:[NSDictionary dictionaryWithObjectsAndKeys: @"anonymous", @"uid", [self labelForKey: @"Public Access"], @"displayName", @"public-user", @"userClass", nil] forKey: @"anonymous"]; prepared = YES; } diff --git a/UI/Common/UIxUserRightsEditor.m b/UI/Common/UIxUserRightsEditor.m index 3519abc0e..7cea2e989 100644 --- a/UI/Common/UIxUserRightsEditor.m +++ b/UI/Common/UIxUserRightsEditor.m @@ -187,11 +187,25 @@ { id response; SOGoDomainDefaults *dd; + NSDictionary *jsonObject, *currentObject; + NSEnumerator *enumerator; + NSArray *o; + id key; - if (![self _initRights]) - response = [NSException exceptionWithHTTPStatus: 403 - reason: @"No such user."]; - else + value = [[self context] request]; + jsonObject = [[value contentAsString] objectFromJSONString]; + enumerator = [jsonObject keyEnumerator]; + + while((key = [enumerator nextObject])) + { + currentObject = [jsonObject objectForKey: key]; + if(![self _initRightsWithParameter: [currentObject objectForKey: @"uid"]]) + { + response = [self responseWithStatus: 403 + andString: @"No such user."]; + return response; + } + else { NSArray *o; diff --git a/UI/MainUI/SOGoUserHomePage.m b/UI/MainUI/SOGoUserHomePage.m index f3a517549..66bc6b0d5 100644 --- a/UI/MainUI/SOGoUserHomePage.m +++ b/UI/MainUI/SOGoUserHomePage.m @@ -383,7 +383,8 @@ NSString *uid; NSDictionary *contact; NSString *contactInfo, *login; - NSMutableArray *jsonResponse, *jsonLine; + NSMutableArray *jsonResponse; + NSMutableDictionary *jsonLine; NSArray *allUsers; int count, max; BOOL activeUserIsInDomain; @@ -405,16 +406,15 @@ // We do NOT return the current authenticated user if (!activeUserIsInDomain || ![uid isEqualToString: login]) { - jsonLine = [NSMutableArray arrayWithCapacity: 4]; + jsonLine = [NSMutableDictionary dictionary]; if ([domain length]) uid = [NSString stringWithFormat: @"%@@%@", uid, domain]; - [jsonLine addObject: uid]; - [jsonLine addObject: [contact objectForKey: @"cn"]]; - [jsonLine addObject: [contact objectForKey: @"c_email"]]; - [jsonLine addObject: [NSNumber numberWithBool: [[contact objectForKey: @"isGroup"] boolValue]]]; + [jsonLine setObject: uid forKey: @"uid"]; + [jsonLine setObject: [NSString stringWithFormat:@"%@ <%@>",[contact objectForKey: @"cn"], [contact objectForKey: @"c_email"]] forKey: @"displayName"]; + [jsonLine setObject: [NSNumber numberWithBool: [[contact objectForKey: @"isGroup"] boolValue]] forKey: @"isGroup"]; contactInfo = [contact objectForKey: @"c_info"]; if (contactInfo) - [jsonLine addObject: contactInfo]; + [jsonLine setObject: contactInfo forKey: @"c_info"]; [jsonResponse addObject: jsonLine]; } } diff --git a/UI/Templates/Themes/mobile/ContactsUI/UIxContactFoldersView.wox b/UI/Templates/Themes/mobile/ContactsUI/UIxContactFoldersView.wox index 501612dd0..44e64b04a 100644 --- a/UI/Templates/Themes/mobile/ContactsUI/UIxContactFoldersView.wox +++ b/UI/Templates/Themes/mobile/ContactsUI/UIxContactFoldersView.wox @@ -8,7 +8,7 @@ xmlns:label="OGo:label" xmlns:rsrc="OGo:url" const:userDefaultsKeys="SOGoContactsCategories" - const:jsFiles="Common/resource.js, Contacts/card-model.js, Contacts/addressbook-model.js" + const:jsFiles="Common/resource.js, Common/acl-model.js, Contacts/card-model.js, Contacts/addressbook-model.js" className="UIxPageFrame" title="name" var:popup="isPopup"> @@ -364,4 +364,55 @@ + diff --git a/UI/WebServerResources/js/Common/acl-model.js b/UI/WebServerResources/js/Common/acl-model.js new file mode 100644 index 000000000..4d7de00c8 --- /dev/null +++ b/UI/WebServerResources/js/Common/acl-model.js @@ -0,0 +1,59 @@ +(function() { + 'use strict'; + + function AclUsers(addressbook) { + this.addressbook_id = addressbook.id; + this.addressbook_name = addressbook.name; + this.addressbook_owner = addressbook.owner; + } + + /* The factory we'll use to register with Angular */ + AclUsers.factory = ['$q', '$timeout', 'sgSettings', 'sgResource', function($q, $timeout, Settings, Resource) { + angular.extend(AclUsers, { + $q: $q, + $timeout: $timeout, + $$resource: new Resource(Settings.baseURL) + }); + + return AclUsers; // return constructor + }]; + + /* Factory registration in Angular module */ + angular.module('SOGo.Common').factory('sgAclUsers', AclUsers.factory); + + /* Instance methods + * Public method, assigned to prototype + */ + AclUsers.prototype.getUsers = function() { + return AclUsers.$$resource.fetch(this.addressbook_id, "getUsersForObject"); + }; + + AclUsers.prototype.searchUsers = function(inputText) { + var param = "search=" + inputText; + return AclUsers.$$resource.fetch(null, "usersSearch", param); + }; + + AclUsers.prototype.openRightsForUserId = function(user) { + var param = "uid=" + user; + return AclUsers.$$resource.fetch(this.addressbook_id, "userRights", param); + }; + + AclUsers.prototype.addUser = function(user) { + var param = "uid=" + user; + AclUsers.$$resource.fetch(this.addressbook_id, "addUserInAcls", param); + }; + + AclUsers.prototype.subscribeUsers = function(users) { + var param = "uids=" + users; + AclUsers.$$resource.fetch(this.addressbook_id, "subscribeUsers", param); + }; + + AclUsers.prototype.removeUser = function(user) { + var userId = "uid=" + user.uid; + AclUsers.$$resource.fetch(this.addressbook_id, "removeUserFromAcls", userId); + }; + + AclUsers.prototype.saveUsersRights = function(dirtyObjects) { + AclUsers.$$resource.saveAclUsers(this.addressbook_id, "saveUserRights", dirtyObjects); + }; +})(); \ No newline at end of file diff --git a/UI/WebServerResources/js/mobile/ContactsUI.js b/UI/WebServerResources/js/mobile/ContactsUI.js index 41634a465..ef4eba5f4 100644 --- a/UI/WebServerResources/js/mobile/ContactsUI.js +++ b/UI/WebServerResources/js/mobile/ContactsUI.js @@ -107,6 +107,49 @@ $urlRouterProvider.otherwise('/app/addressbooks'); }) + .directive('ionSearch', function() { + return { + restrict: 'E', + replace: true, + scope: { + getData: '&source', + clearData: '&clear', + model: '=?', + search: '=?filter' + }, + link: function(scope, element, attrs) { + attrs.minLength = attrs.minLength || 0; + scope.placeholder = attrs.placeholder || ''; + scope.search = {value: ''}; + + if (attrs.class) + element.addClass(attrs.class); + + if (attrs.source) { + scope.$watch('search.value', function (newValue, oldValue) { + if (newValue.length > attrs.minLength) { + scope.getData({str: newValue}).then(function (results) { + scope.model = results; + }); + } + else { + scope.model = []; + } + }); + } + scope.clearSearch = function() { + scope.search.value = ''; + scope.clearData(); + }; + }, + template: '
' + + '' + + '' + + '' + + '
' + }; + }) + .controller('AppCtrl', ['$scope', '$http', function($scope, $http) { $scope.UserLogin = UserLogin; $scope.UserFolderURL = UserFolderURL; @@ -119,7 +162,7 @@ // }; }]) - .controller('AddressBooksCtrl', ['$scope', '$rootScope', '$ionicModal', '$ionicListDelegate', '$ionicActionSheet', 'sgDialog', 'sgAddressBook', function($scope, $rootScope, $ionicModal, $ionicListDelegate, $ionicActionSheet, Dialog, AddressBook) { + .controller('AddressBooksCtrl', ['$scope', '$rootScope', '$ionicModal', '$ionicListDelegate', '$ionicActionSheet', 'sgDialog', 'sgAddressBook', 'sgAclUsers', function($scope, $rootScope, $ionicModal, $ionicListDelegate, $ionicActionSheet, Dialog, AddressBook, sgAclUsers) { // Initialize with data from template $scope.addressbooks = AddressBook.$findAll(contactFolders); $scope.newAddressbook = function() { @@ -141,19 +184,181 @@ $scope.edit = function(addressbook) { $ionicActionSheet.show({ buttons: [ - { text: l('Rename') } + { text: l('Rename') }, + { text: l('Sharing') } ], destructiveText: l('Delete'), cancelText: l('Cancel'), buttonClicked: function(index) { - // Rename addressbook - Dialog.prompt(l('Rename addressbook'), - addressbook.name) + if(index == 0) { + // Rename addressbook + Dialog.prompt(l('Rename addressbook'), + addressbook.name) .then(function(name) { if (name && name.length > 0) { addressbook.$rename(name); } }); + } + else if(index == 1) { + // Build modal editor + $ionicModal.fromTemplateUrl('acl-modal.html', { scope: $scope }).then(function(modal) { + if ($scope.$aclEditorModal) { + $scope.$aclEditorModal.remove(); + } + // Variables in scope + $scope.$aclEditorModal = modal; + $scope.addressbook = addressbook; + $scope.AclUsers = new sgAclUsers(addressbook); + var aclUsers = {}; + $scope.AclUsers.getUsers().then(function(users) { + $scope.refreshUsers(users); + }); + $scope.showDelete = false; + $scope.onGoingSearch = true; + + // Variables in javascript + var dirtyObjects = {}; + + // Local functions + $scope.refreshUsers = function(users) { + $scope.users = []; + $scope.onGoingSearch = true; + angular.forEach(users, function(user){ + user.inAclList = true; + user.canSubscribeUser = (user.isSubscribed) ? false : true; + $scope.users.push(user); + aclUsers[user.uid] = user; + }) + }; + + // Function in scope + $scope.closeModal = function() { + $scope.$aclEditorModal.remove(); + }; + $scope.saveModal = function() { + if(Object.keys(dirtyObjects).length > 0) { + if(dirtyObjects["anonymous"]) + { + Dialog.confirm(l("Warning"), l("Any user with an account on this system will be able to access your mailbox \"%{0}\". Are you certain you trust them all?")).then(function(res){ + if(res){Dialog.alert("okay!")}; + }) + } + else if (dirtyObjects[""]) { + Dialog.confirm(l("Warning"), l("Potentially anyone on the Internet will be able to access your calendar \"%{0}\", even if they do not have an account on this system. Is this information suitable for the public Internet?")).then(function(res){ + if(res){Dialog.alert("okay!")}; + }) + } + else { + $scope.AclUsers.saveUsersRights(dirtyObjects); + var usersToSubscribe = []; + angular.forEach(dirtyObjects, function(dirtyObject){ + if(dirtyObject.canSubscribeUser && dirtyObject.isSubscribed){ + usersToSubscribe.push(dirtyObject.uid); + } + }) + $scope.AclUsers.subscribeUsers(usersToSubscribe); + } + } + $scope.$aclEditorModal.remove(); + }; + $scope.cancelSearch = function() { + $scope.AclUsers.getUsers().then(function(users) { + $scope.refreshUsers(users); + }); + }; + $scope.toggleDelete = function(boolean) { + $scope.showDelete = boolean; + }; + $scope.removeUser = function(user) { + if (user) { + if(dirtyObjects[user.uid]) + delete dirtyObjects[user.uid]; + delete aclUsers[user.uid]; + $scope.AclUsers.removeUser(user); + $scope.AclUsers.getUsers().then(function(users) { + $scope.refreshUsers(users); + }); + $scope.userSelected = {}; + } + }; + $scope.addUser = function (user) { + if (user) { + $scope.AclUsers.addUser(user.uid); + $scope.AclUsers.getUsers().then(function(users) { + $scope.refreshUsers(users); + }); + } + else { + // TODO : Write a better msg and add the string inside the .string + Dialog.alert(l('Warning'), l('This user is already added to your AclUsers list')); + } + }; + $scope.editUser = function(user) { + if ($scope.userSelected != user){ + $scope.userSelected = user; + + if (dirtyObjects[$scope.userSelected.uid]) { + // If the user already made changes on the user rights, it is saved inside an object called dirty. + // We preverse these changes untill the user decide to save or discard them. + $scope.userSelected.aclOptions = dirtyObjects[$scope.userSelected.uid].aclOptions; + } + else { + // Otherwise, if it's the first time the user consult the user rights; fetch from server + $scope.AclUsers.openRightsForUserId($scope.userSelected.uid).then(function(userRights) { + $scope.userSelected.aclOptions = userRights; + }); + } + } + }; + $scope.getContacts = function(value){ + $scope.users = []; + $scope.onGoingSearch = false; + return $scope.AclUsers.searchUsers(value).then(function(usersFound) { + angular.forEach(usersFound, function(userFound){ + userFound.inAclList = (aclUsers[userFound.uid]) ? true : false; + $scope.users.push(userFound); + }) + }); + }; + $scope.toggleUser = function(user) { + if (user.inAclList) { + if ($scope.isUserShown(user)) { + $scope.shownUser = null; + } + else { + $scope.shownUser = user; + $scope.editUser(user); + } + } + else { + $scope.addUser(user); + } + }; + $scope.isUserShown = function(user) { + return $scope.shownUser === user; + }; + $scope.markUserAsDirty = function() { + dirtyObjects[$scope.userSelected.uid] = $scope.userSelected; + }; + $scope.displayUserRights = function() { + // Does the rights applies on the user/group + return ($scope.userSelected && $scope.userSelected.uid != "anonymous") ? true : false; + }; + $scope.displaySubscribeUser = function() { + // Is the user/group available for subscription + return ($scope.userSelected && !($scope.userSelected.uid == "anonymous" || $scope.userSelected.uid == "")) ? true : false; + }; + $scope.displayIcon = function(user) { + if (user.inAclList) + return ($scope.isUserShown(user) ? 'ion-ios7-arrow-down' : 'ion-ios7-arrow-right'); + else + return 'ion-plus'; + } + // Show modal + $scope.$aclEditorModal.show(); + }); + } return true; }, destructiveButtonClicked: function() {