1/* UIxFolderActions.m - this file is part of SOGo
2 *
3 * Copyright (C) 2007-2013 Inverse inc.
4 *
5 * This file is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
8 * any later version.
9 *
10 * This file is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; see the file COPYING.  If not, write to
17 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
19 */
20
21#import <Foundation/NSDictionary.h>
22#import <Foundation/NSEnumerator.h>
23#import <Foundation/NSString.h>
24#import <Foundation/NSURL.h>
25#import <Foundation/NSValue.h>
26
27#import <NGObjWeb/WOContext.h>
28#import <NGObjWeb/WOContext+SoObjects.h>
29#import <NGObjWeb/WORequest.h>
30#import <NGObjWeb/WOResponse.h>
31#import <NGObjWeb/SoSecurityManager.h>
32#import <NGObjWeb/SoClassSecurityInfo.h>
33#import <NGObjWeb/NSException+HTTP.h>
34
35#import <SoObjects/SOGo/SOGoUserManager.h>
36#import <SoObjects/SOGo/NSArray+Utilities.h>
37#import <SoObjects/SOGo/SOGoContentObject.h>
38#import <SoObjects/SOGo/SOGoGCSFolder.h>
39#import <SoObjects/SOGo/SOGoParentFolder.h>
40#import <SoObjects/SOGo/SOGoPermissions.h>
41#import <SoObjects/SOGo/SOGoUser.h>
42#import <SoObjects/SOGo/SOGoUserSettings.h>
43
44#import "WODirectAction+SOGo.h"
45
46#import "UIxFolderActions.h"
47
48@implementation UIxFolderActions
49
50- (void) _setupContext
51{
52  NSString *mailInvitationParam;
53  SOGoUser *activeUser;
54
55  activeUser = [context activeUser];
56  login = [activeUser login];
57  clientObject = [self clientObject];
58  owner = [clientObject ownerInContext: nil];
59
60  baseFolder = [[clientObject container] nameInContainer];
61
62  um = [SOGoUserManager sharedUserManager];
63  us = [activeUser userSettings];
64  moduleSettings = [us objectForKey: baseFolder];
65  if (!moduleSettings)
66    moduleSettings = [NSMutableDictionary dictionary];
67  [us setObject: moduleSettings forKey: baseFolder];
68
69  mailInvitationParam
70    = [[context request] formValueForKey: @"mail-invitation"];
71  isMailInvitation = [mailInvitationParam boolValue];
72}
73
74- (WOResponse *) _subscribeAction: (BOOL) reallyDo
75{
76  WOResponse *response;
77  NSURL *mailInvitationURL;
78
79  response = [context response];
80  [response setHeader: @"text/plain; charset=utf-8"
81               forKey: @"Content-Type"];
82
83  [self _setupContext];
84  if ([owner isEqualToString: login])
85    {
86      [response setStatus: 403];
87      [response appendContentString:
88                  @"You cannot (un)subscribe to a folder that you own!"];
89    }
90  else
91    {
92      [clientObject subscribeUserOrGroup: login
93                                reallyDo: reallyDo
94                                response: response];
95
96      if (isMailInvitation)
97        {
98          mailInvitationURL
99            = [clientObject soURLToBaseContainerForCurrentUser];
100          [response setStatus: 302];
101          [response setHeader: [mailInvitationURL absoluteString]
102                       forKey: @"location"];
103        }
104      else
105        [response setStatus: 204];
106    }
107
108  return response;
109}
110
111- (WOResponse *) subscribeAction
112{
113  return [self _subscribeAction: YES];
114}
115
116- (WOResponse *) unsubscribeAction
117{
118  return [self _subscribeAction: NO];
119}
120
121- (WOResponse *) canAccessContentAction
122{
123  /* We want this action to be authorized managed by the SOPE's internal acl
124     handling. */
125  return [self responseWith204];
126// #warning IMPROVEMENTS REQUIRED!
127//   NSArray *acls;
128// //  NSEnumerator *userAcls;
129// //  NSString *currentAcl;
130
131//   [self _setupContext];
132
133// //  NSLog(@"canAccessContentAction %@, owner %@", subscriptionPointer, owner);
134
135//   if ([login isEqualToString: owner] || [owner isEqualToString: @"nobody"]) {
136//     return [self responseWith204];
137//   }
138//   else {
139//     acls = [clientObject aclsForUser: login];
140// //    userAcls = [acls objectEnumerator];
141// //    currentAcl = [userAcls nextObject];
142// //    while (currentAcl) {
143// //      NSLog(@"ACL login %@, owner %@, folder %@: %@",
144// //	    login, owner, baseFolder, currentAcl);
145// //      currentAcl = [userAcls nextObject];
146// //    }
147//     if (([[clientObject folderType] isEqualToString: @"Contact"]
148// 	 && [acls containsObject: SOGoRole_ObjectViewer]) ||
149// 	([[clientObject folderType] isEqualToString: @"Appointment"]
150// 	 && [acls containsObject: SOGoRole_AuthorizedSubscriber])) {
151//       return [self responseWith204];
152//     }
153//   }
154
155//   return [self responseWithStatus: 403];
156}
157
158- (WOResponse *) _realFolderActivation: (BOOL) makeActive
159{
160  NSMutableArray *folderSubscription;
161  NSString *folderName;
162
163  [self _setupContext];
164  folderSubscription
165    = [moduleSettings objectForKey: @"InactiveFolders"];
166  if (!folderSubscription)
167    {
168      folderSubscription = [NSMutableArray array];
169      [moduleSettings setObject: folderSubscription forKey: @"InactiveFolders"];
170    }
171
172  folderName = [clientObject nameInContainer];
173  if (makeActive)
174    [folderSubscription removeObject: folderName];
175  else
176    [folderSubscription addObjectUniquely: folderName];
177
178  [us synchronize];
179
180  return [self responseWith204];
181}
182
183- (WOResponse *) activateFolderAction
184{
185  return [self _realFolderActivation: YES];
186}
187
188- (WOResponse *) deactivateFolderAction
189{
190  return [self _realFolderActivation: NO];
191}
192
193- (WOResponse *) renameFolderAction
194{
195  WOResponse *response;
196  NSString *folderName;
197
198  folderName = [[context request] formValueForKey: @"name"];
199  if ([folderName length] > 0)
200    {
201      clientObject = [self clientObject];
202      [clientObject renameTo: folderName];
203      response = [self responseWith204];
204    }
205  else
206    {
207      response = [self responseWithStatus: 500];
208      [response appendContentString: @"Missing 'name' parameter."];
209    }
210
211  return response;
212}
213
214- (id) batchDeleteAction
215{
216  WOResponse *response;
217  NSString *idsParam;
218  NSArray *ids;
219
220  idsParam = [[context request] formValueForKey: @"ids"];
221  ids = [idsParam componentsSeparatedByString: @","];
222  if ([ids count])
223    {
224      clientObject = [self clientObject];
225      [clientObject deleteEntriesWithIds: ids];
226      response = [self responseWith204];
227    }
228  else
229    {
230      response = [self responseWithStatus: 500];
231      [response appendContentString: @"At least 1 id required."];
232    }
233
234  return response;
235}
236
237- (NSException*) _moveContacts: (NSArray*) contactsId
238		      toFolder: (NSString*) destinationFolderId
239		   andKeepCopy: (BOOL) keepCopy
240{
241  NSEnumerator *uids;
242  NSException *ex;
243  NSString *uid;
244  SOGoContentObject *currentChild;
245  SOGoGCSFolder *sourceFolder, *destinationFolder;
246  SOGoParentFolder *folders;
247  SoSecurityManager *sm;
248  unsigned int errorCount;
249
250  sm = [SoSecurityManager sharedSecurityManager];
251  ex = nil;
252  errorCount = 0;
253
254  // Search the specified destination folder
255  sourceFolder = [self clientObject];
256  folders = [sourceFolder container];
257  destinationFolder = [folders lookupName: destinationFolderId
258			       inContext: nil
259			       acquire: NO];
260  if (destinationFolder)
261    {
262      // Verify write access to the folder
263      ex = [sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
264	       onObject: destinationFolder
265	       inContext: context];
266      if (!ex)
267	{
268	  uids = [contactsId objectEnumerator];
269	  while ((uid = [uids nextObject]))
270	    {
271	      // Search the currentChild ID
272	      currentChild = [sourceFolder lookupName: uid
273					   inContext: [self context]
274					   acquire: NO];
275	      if ([currentChild isKindOfClass: [NSException class]])
276		errorCount++;
277	      else
278		{
279		  if (keepCopy)
280		    ex = [currentChild copyToFolder: destinationFolder];
281		  else
282		    ex = [currentChild moveToFolder: destinationFolder];
283		  if (ex)
284		    errorCount++;
285		}
286	    }
287	}
288    }
289  else
290    ex = [NSException exceptionWithName: @"UnknownDestinationFolder"
291		      reason: @"Unknown Destination Folder"
292		      userInfo: nil];
293
294  if (errorCount > 0)
295    // At least one currentChild was not copied
296    ex = [NSException exceptionWithHTTPStatus: 400
297		      reason: @"Invalid Contact"];
298  else if (ex != nil)
299    // Destination address book doesn't exist or is not writable
300    ex = [NSException exceptionWithHTTPStatus: 403
301		      reason: [ex name]];
302
303  return ex;
304}
305
306- (id <WOActionResults>) copyAction
307{
308  WORequest *request;
309  id <WOActionResults> response;
310  NSString *destinationFolderId;
311  NSArray *contactsId;
312  NSException *ex;
313
314  request = [context request];
315
316  if ((destinationFolderId = [request formValueForKey: @"folder"]) &&
317      (contactsId = [request formValuesForKey: @"uid"]))
318    ex = [self _moveContacts: contactsId
319                    toFolder: destinationFolderId
320		 andKeepCopy: YES];
321  else
322    ex = [NSException exceptionWithHTTPStatus: 400
323                                       reason: (@"missing 'folder' and/or"
324                                                @" 'uid' parameter")];
325
326  if (ex)
327    response = (id) ex;
328  else
329    response = [self responseWith204];
330
331  return response;
332}
333
334- (id <WOActionResults>) moveAction
335{
336  WORequest *request;
337  id <WOActionResults> response;
338  NSString *destinationFolderId;
339  NSArray *contactsId;
340  NSException *ex;
341
342  request = [context request];
343
344  if ((destinationFolderId = [request formValueForKey: @"folder"])
345      && (contactsId = [request formValuesForKey: @"uid"]))
346    ex = [self _moveContacts: contactsId
347                    toFolder: destinationFolderId
348		 andKeepCopy: NO];
349  else
350    ex = [NSException exceptionWithHTTPStatus: 400
351                                       reason: (@"missing 'folder' and/or"
352                                                @"'uid' parameter")];
353
354  if (ex)
355    response = (id <WOActionResults>) ex;
356  else
357    response = [self responseWith204];
358
359  return response;
360}
361
362- (id <WOActionResults>) subscribeUsersAction
363{
364  id <WOActionResults> response;
365  NSString *uids;
366  NSArray *userIDs;
367  SOGoGCSFolder *folder;
368  NSException *ex;
369  int count, max;
370
371  uids = [[context request] formValueForKey: @"uids"];
372  if ([uids length])
373    {
374      userIDs = [uids componentsSeparatedByString: @","];
375      folder = [self clientObject];
376      max = [userIDs count];
377      for (count = 0; count < max; count++)
378        [folder subscribeUserOrGroup: [userIDs objectAtIndex: count]
379			    reallyDo: YES
380                            response: nil];
381      ex = nil;
382    }
383  else
384    ex = [NSException exceptionWithHTTPStatus: 400
385                                       reason: @"missing 'uids' parameter"];
386
387  if (ex)
388    response = (id <WOActionResults>) ex;
389  else
390    response = [self responseWith204];
391
392  return response;
393}
394
395@end
396