1/*
2  Copyright (C) 2006-2015 Inverse inc.
3  Copyright (C) 2005 SKYRIX Software AG
4
5  This file is part of SOGo.
6
7  SOGo is free software; you can redistribute it and/or modify it under
8  the terms of the GNU Lesser General Public License as published by the
9  Free Software Foundation; either version 2, or (at your option) any
10  later version.
11
12  SOGo is distributed in the hope that it will be useful, but WITHOUT ANY
13  WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
15  License for more details.
16
17  You should have received a copy of the GNU Lesser General Public
18  License along with OGo; see the file COPYING.  If not, write to the
19  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
20  02111-1307, USA.
21*/
22
23#import <Foundation/NSArray.h>
24#import <Foundation/NSCalendarDate.h>
25#import <Foundation/NSDictionary.h>
26#import <Foundation/NSEnumerator.h>
27#import <Foundation/NSNull.h>
28#import <Foundation/NSTimeZone.h>
29#import <Foundation/NSValue.h>
30#import <Foundation/NSURL.h>
31#import <NGObjWeb/WOApplication.h>
32#import <NGObjWeb/WOContext.h>
33#import <NGObjWeb/WORequest.h>
34#import <NGObjWeb/SoObject.h>
35#import <NGExtensions/NSCalendarDate+misc.h>
36#import <NGExtensions/NSNull+misc.h>
37#import <NGExtensions/NSObject+Logs.h>
38
39#import <Appointments/SOGoAppointmentFolders.h>
40#import <Contacts/SOGoContactFolders.h>
41
42#import "NSArray+Utilities.h"
43#import "SOGoCache.h"
44#import "SOGoDateFormatter.h"
45#import "SOGoDomainDefaults.h"
46#import "SOGoObject.h"
47#import "SOGoPermissions.h"
48#import "SOGoSystemDefaults.h"
49#import "SOGoUserDefaults.h"
50#import "SOGoUserFolder.h"
51#import "SOGoUserManager.h"
52#import "SOGoUserProfile.h"
53#import "SOGoUserSettings.h"
54#import "WOResourceManager+SOGo.h"
55
56#import "SOGoUser.h"
57
58@implementation SoUser (SOGoExtension)
59
60- (SOGoDomainDefaults *) userDefaults
61{
62  return [SOGoSystemDefaults sharedSystemDefaults];
63}
64
65- (SOGoDomainDefaults *) domainDefaults
66{
67  return [SOGoSystemDefaults sharedSystemDefaults];
68}
69
70@end
71
72@implementation SOGoUser
73
74// + (NSString *) language
75// {
76//   NSArray *bLanguages;
77//   WOContext *context;
78//   NSString *lng;
79
80//   context = [[WOApplication application] context];
81//   bLanguages = [[context request] browserLanguages];
82//   if ([bLanguages count] > 0)
83//     lng = [bLanguages objectAtIndex: 0];
84
85//   if (![lng length])
86//     lng = defaultLanguage;
87
88//   return lng;
89// }
90
91+ (SOGoUser *) userWithLogin: (NSString *) newLogin
92{
93  return [self userWithLogin: newLogin  roles: nil];
94}
95
96+ (SOGoUser *) userWithLogin: (NSString *) newLogin
97		       roles: (NSArray *) newRoles
98{
99  return [self userWithLogin: newLogin  roles: newRoles  trust: NO];
100}
101
102+ (SOGoUser *) userWithLogin: (NSString *) newLogin
103		       roles: (NSArray *) newRoles
104		       trust: (BOOL) b
105{
106  SOGoCache *cache;
107  SOGoUser *user;
108
109  cache = [SOGoCache sharedCache];
110  user = [cache userNamed: newLogin];
111  if (!user)
112    {
113      user = [[self alloc] initWithLogin: newLogin roles: newRoles trust: b];
114      if (user)
115	{
116 	  [cache registerUser: user withName: newLogin];
117	  [user release];
118	}
119    }
120  if (newRoles)
121    [user setPrimaryRoles: newRoles];
122
123  return user;
124}
125
126/**
127 * Return a new instance for the login name, which can be appended by a
128 * domain name. The domain is extracted only if the system defaults
129 * SOGoEnableDomainBasedUID is enabled.
130 *
131 * @param newLogin a login name optionally follow by @domain
132 * @param newRoles
133 * @param b is set to YES if newLogin can be trust
134 * @see loginInDomain
135 * @see [SOGoSession decodeValue:usingKey:login:domain:password:]
136 */
137- (id) initWithLogin: (NSString *) newLogin
138	       roles: (NSArray *) newRoles
139	       trust: (BOOL) b
140{
141  SOGoUserManager *um;
142  SOGoSystemDefaults *sd;
143  NSDictionary *contactInfos;
144  NSString *realUID, *uid, *domain;
145  NSRange r;
146
147  _defaults = nil;
148  _settings = nil;
149
150  uid = nil;
151  realUID = nil;
152  domain = nil;
153
154  if ([newLogin isEqualToString: @"anonymous"]
155      || [newLogin isEqualToString: @"freebusy"])
156    realUID = newLogin;
157  else
158    {
159      sd = [SOGoSystemDefaults sharedSystemDefaults];
160      if ([sd enableDomainBasedUID] || [[sd loginDomains] count] > 0)
161        {
162          r = [newLogin rangeOfString: @"@" options: NSBackwardsSearch];
163          if (r.location != NSNotFound)
164            {
165              // The domain is probably appended to the username;
166              // make sure it is defined as a domain in the configuration.
167              domain = [newLogin substringFromIndex: (r.location + r.length)];
168              if ([[SOGoUserManager sharedUserManager] isDomainDefined: domain] &&
169                  ![sd enableDomainBasedUID])
170                newLogin = [newLogin substringToIndex: r.location];
171
172              if (domain != nil && ![sd enableDomainBasedUID])
173                // Login domains are enabled (SOGoLoginDomains) but not
174                // domain-based UID (SOGoEnableDomainBasedUID).
175                // Drop the domain from the login name.
176                domain = nil;
177            }
178        }
179
180      newLogin = [newLogin stringByReplacingString: @"%40"
181                                        withString: @"@"];
182      if (b)
183	realUID = newLogin;
184      else
185	{
186	  um = [SOGoUserManager sharedUserManager];
187          contactInfos = [um contactInfosForUserWithUIDorEmail: newLogin
188                                                      inDomain: domain];
189	  realUID = [contactInfos objectForKey: @"c_uid"];
190          if (domain == nil && [sd enableDomainBasedUID])
191            domain = [contactInfos objectForKey: @"c_domain"];
192	}
193
194      if ([realUID length] && [domain length])
195        {
196          // When the user is associated to a domain, the [SOGoUser login]
197          // method returns the combination login@domain while
198          // [SOGoUser loginInDomain] only returns the login.
199          r = [realUID rangeOfString: domain  options: NSBackwardsSearch|NSCaseInsensitiveSearch];
200
201          // Do NOT strip @domain.com if SOGoEnableDomainBasedUID is enabled since
202          // the real login most likely is the email address.
203          if (r.location != NSNotFound && ![sd enableDomainBasedUID])
204            uid = [realUID substringToIndex: r.location-1];
205          // If we don't have the domain in the UID but SOGoEnableDomainBasedUID is
206          // enabled, let's add it internally so so it becomes unique across
207          // all potential domains.
208          else if (r.location == NSNotFound && [sd enableDomainBasedUID])
209            {
210              uid = [NSString stringWithString: realUID];
211              realUID = [NSString stringWithFormat: @"%@@%@", realUID, domain];
212            }
213          // We found the domain and SOGoEnableDomainBasedUID is enabled,
214          // we keep realUID.. This would happen for example if the user
215          // authenticates with foo@bar.com and the UIDFieldName is also foo@bar.com
216          else if ([sd enableDomainBasedUID])
217            uid = [NSString stringWithString: realUID];
218        }
219    }
220
221  if ([realUID length])
222    {
223      if ((self = [super initWithLogin: realUID roles: newRoles]))
224	{
225	  allEmails = nil;
226	  currentPassword = nil;
227	  cn = nil;
228          ASSIGN (loginInDomain, (uid ? uid : realUID));
229          _defaults = nil;
230          _domainDefaults = nil;
231          _settings = nil;
232          mailAccounts = nil;
233	}
234    }
235  else
236    {
237      [self release];
238      self = nil;
239    }
240
241  return self;
242}
243
244- (void) dealloc
245{
246  [_defaults release];
247  [_domainDefaults release];
248  [_settings release];
249  [allEmails release];
250  [mailAccounts release];
251  [currentPassword release];
252  [cn release];
253  [loginInDomain release];
254  [super dealloc];
255}
256
257- (void) setPrimaryRoles: (NSArray *) newRoles
258{
259  ASSIGN (roles, newRoles);
260}
261
262- (void) setCurrentPassword: (NSString *) newPassword
263{
264  ASSIGN (currentPassword, newPassword);
265}
266
267- (NSString *) currentPassword
268{
269  return currentPassword;
270}
271
272- (NSString *) loginInDomain
273{
274  return loginInDomain;
275}
276
277- (id) _fetchFieldForUser: (NSString *) field
278{
279  NSDictionary *contactInfos;
280  SOGoUserManager *um;
281
282  um = [SOGoUserManager sharedUserManager];
283  contactInfos = [um contactInfosForUserWithUIDorEmail: login];
284
285  return [contactInfos objectForKey: field];
286}
287
288- (void) _fetchAllEmails
289{
290  allEmails = [self _fetchFieldForUser: @"emails"];
291  [allEmails retain];
292}
293
294- (void) _fetchCN
295{
296  cn = [[self _fetchFieldForUser: @"cn"] stringByTrimmingSpaces];
297  [cn retain];
298}
299
300/* properties */
301- (NSString *) domain
302{
303  return [self _fetchFieldForUser: @"c_domain"];
304}
305
306- (id <SOGoSource>) authenticationSource
307{
308  NSString *sourceID;
309  SOGoUserManager *um;
310
311  sourceID = [self _fetchFieldForUser: @"SOGoSource"];
312  um = [SOGoUserManager sharedUserManager];
313
314  return [um sourceWithID: sourceID];
315}
316
317- (NSArray *) allEmails
318{
319  if (!allEmails)
320    [self _fetchAllEmails];
321
322  return allEmails;
323}
324
325//
326// We always return the last object among our list of email addresses. This value
327// is always added in SOGoUserManager: -_fillContactMailRecords:
328//
329- (NSString *) systemEmail
330{
331  if (!allEmails)
332    [self _fetchAllEmails];
333
334  return [allEmails lastObject];
335}
336
337- (BOOL) hasEmail: (NSString *) email
338{
339  if (!allEmails)
340    [self _fetchAllEmails];
341
342  return [allEmails containsCaseInsensitiveString: email];
343}
344
345- (NSString *) cn
346{
347  if (!cn)
348    [self _fetchCN];
349
350  return cn;
351}
352
353- (NSMutableDictionary *) defaultIdentity
354{
355  NSMutableDictionary *currentIdentity, *defaultIdentity;
356  NSEnumerator *identities;
357
358  defaultIdentity = nil;
359
360  identities = [[self allIdentities] objectEnumerator];
361  while (!defaultIdentity
362	 && (currentIdentity = [identities nextObject]))
363    if ([[currentIdentity objectForKey: @"isDefault"] boolValue])
364      defaultIdentity = currentIdentity;
365
366  return defaultIdentity;
367}
368
369- (SOGoDateFormatter *) dateFormatterInContext: (WOContext *) context
370{
371  SOGoDateFormatter *dateFormatter;
372  NSString *format;
373  SOGoUserDefaults *ud;
374  NSDictionary *locale;
375  WOResourceManager *resMgr;
376
377  dateFormatter = [SOGoDateFormatter new];
378  [dateFormatter autorelease];
379
380  ud = [self userDefaults];
381  resMgr = [[WOApplication application] resourceManager];
382  locale = [resMgr localeForLanguageNamed: [ud language]];
383  [dateFormatter setLocale: locale];
384  format = [ud shortDateFormat];
385  if (format)
386    [dateFormatter setShortDateFormat: format];
387  format = [ud longDateFormat];
388  if (format)
389    [dateFormatter setLongDateFormat: format];
390  format = [ud timeFormat];
391  if (format)
392    [dateFormatter setTimeFormat: format];
393
394  return dateFormatter;
395}
396
397- (SOGoUserDefaults *) userDefaults
398{
399  if (!_defaults)
400    {
401      _defaults = [SOGoUserDefaults defaultsForUser: login
402                                           inDomain: [self domain]];
403      [_defaults retain];
404    }
405  //else
406  //  NSLog(@"User defaults cache hit for %@", login);
407
408  return _defaults;
409}
410
411- (SOGoDomainDefaults *) domainDefaults
412{
413  NSString *domain;
414
415  if (!_domainDefaults)
416    {
417      domain = [self domain];
418      if ([domain length])
419        {
420          _domainDefaults = [SOGoDomainDefaults defaultsForDomain: domain];
421          if (!_domainDefaults)
422            {
423              //[self errorWithFormat: @"domain '%@' does not exist!", domain];
424              _domainDefaults = [SOGoSystemDefaults sharedSystemDefaults];
425            }
426        }
427      else
428        _domainDefaults = [SOGoSystemDefaults sharedSystemDefaults];
429      [_domainDefaults retain];
430    }
431  //else
432  //  NSLog(@"User defaults cache hit for %@", login);
433
434  return _domainDefaults;
435}
436
437- (SOGoUserSettings *) userSettings
438{
439  if (!_settings)
440    {
441      _settings = [SOGoUserSettings settingsForUser: login];
442      [_settings retain];
443    }
444
445  return _settings;
446}
447
448- (NSCalendarDate *) firstDayOfWeekForDate: (NSCalendarDate *) date
449{
450  int offset;
451  NSCalendarDate *firstDay;
452
453  offset = [[self userDefaults] firstDayOfWeek] - [date dayOfWeek];
454  if (offset > 0)
455    offset -= 7;
456
457  firstDay = [date addTimeInterval: offset * 86400];
458
459  return firstDay;
460}
461
462- (unsigned int) dayOfWeekForDate: (NSCalendarDate *) date
463{
464  unsigned int offset, baseDayOfWeek, dayOfWeek;
465
466  offset = [[self userDefaults] firstDayOfWeek];
467  baseDayOfWeek = [date dayOfWeek];
468  if (offset > baseDayOfWeek)
469    baseDayOfWeek += 7;
470
471  dayOfWeek = baseDayOfWeek - offset;
472
473  return dayOfWeek;
474}
475
476- (NSCalendarDate *) firstWeekOfYearForDate: (NSCalendarDate *) date
477{
478  NSString *firstWeekRule;
479  NSCalendarDate *januaryFirst, *firstWeek;
480  unsigned int dayOfWeek;
481
482  firstWeekRule = [[self userDefaults] firstWeekOfYear];
483
484  januaryFirst = [NSCalendarDate dateWithYear: [date yearOfCommonEra]
485				 month: 1 day: 1 hour: 0 minute: 0 second: 0
486				 timeZone: [date timeZone]];
487  if ([firstWeekRule isEqualToString: SOGoWeekStartFirst4DayWeek])
488    {
489      dayOfWeek = [self dayOfWeekForDate: januaryFirst];
490      if (dayOfWeek < 4)
491	firstWeek = [self firstDayOfWeekForDate: januaryFirst];
492      else
493	firstWeek = [self firstDayOfWeekForDate: [januaryFirst
494						   dateByAddingYears: 0
495						   months: 0
496						   days: 7]];
497    }
498  else if ([firstWeekRule isEqualToString: SOGoWeekStartFirstFullWeek])
499    {
500      dayOfWeek = [self dayOfWeekForDate: januaryFirst];
501      if (dayOfWeek == 0)
502	firstWeek = [self firstDayOfWeekForDate: januaryFirst];
503      else
504	firstWeek = [self firstDayOfWeekForDate: [januaryFirst
505						   dateByAddingYears: 0
506						   months: 0
507						   days: 7]];
508    }
509  else
510    firstWeek = [self firstDayOfWeekForDate: januaryFirst];
511
512  return firstWeek;
513}
514
515- (unsigned int) weekNumberForDate: (NSCalendarDate *) date
516{
517  NSCalendarDate *firstWeek, *previousWeek;
518  unsigned int weekNumber;
519
520  firstWeek = [self firstWeekOfYearForDate: date];
521  if ([firstWeek earlierDate: date] == firstWeek)
522    {
523      weekNumber = ([date timeIntervalSinceDate: firstWeek] / (86400 * 7) + 1);
524    }
525  else
526    {
527      // Date is within the last week of the previous year;
528      // Compute the previous week number to find the week number of the requested date.
529      // The number will either be 52 or 53.
530      previousWeek = [date dateByAddingYears: 0
531                                      months: 0
532                                        days: -7];
533      firstWeek = [self firstWeekOfYearForDate: previousWeek];
534      weekNumber = ([previousWeek timeIntervalSinceDate: firstWeek] / (86400 * 7) + 1);
535      weekNumber += 1;
536    }
537
538  return weekNumber;
539}
540
541/* mail */
542- (BOOL) _migrateFolderWithPurpose: (NSString *) purpose
543                          withName: (NSString *) folderName
544{
545  NSString *methodName;
546  SEL methodSel;
547  BOOL rc;
548
549  [self userDefaults];
550  methodName = [NSString stringWithFormat: @"set%@FolderName:", purpose];
551  methodSel = NSSelectorFromString (methodName);
552  if ([_defaults respondsToSelector: methodSel])
553    {
554      [_defaults performSelector: methodSel withObject: folderName];
555      rc = YES;
556    }
557  else
558    {
559      [self errorWithFormat: @"method '%@' not available with user defaults"
560            @" object, folder migration fails", methodName];
561      rc = NO;
562    }
563
564  return rc;
565}
566
567- (void) _migrateFolderSettings
568{
569  NSMutableDictionary *mailSettings;
570  NSString *folderName, *key;
571  BOOL migrated;
572  NSString **purpose;
573  NSString *purposes[] = { @"Drafts", @"Sent", @"Trash", nil };
574
575  [self userSettings];
576  mailSettings = [_settings objectForKey: @"Mail"];
577  if (mailSettings)
578    {
579      migrated = NO;
580      purpose = purposes;
581      while (*purpose)
582        {
583          key = [NSString stringWithFormat: @"%@Folder", *purpose];
584          folderName = [mailSettings objectForKey: key];
585          if ([folderName length]
586              && [self _migrateFolderWithPurpose: *purpose
587                                        withName: folderName])
588            {
589              migrated = YES;
590              [mailSettings removeObjectForKey: key];
591              folderName = nil;
592            }
593          purpose++;
594        }
595      if (migrated)
596        {
597          [_settings synchronize];
598          [self userDefaults];
599          [_defaults synchronize];
600        }
601    }
602}
603
604- (void) _appendSystemMailAccount
605{
606  NSString *fullName, *replyTo, *imapLogin, *imapServer, *cImapServer, *signature,
607    *encryption, *scheme, *action, *query, *customEmail, *defaultEmail, *sieveServer;
608  NSMutableDictionary *mailAccount, *identity, *mailboxes, *receipts;
609  NSNumber *port;
610  NSMutableArray *identities, *mails;
611  NSURL *url, *cUrl;
612  unsigned int count, max, default_identity;
613  NSInteger defaultPort;
614  NSUInteger index;
615
616  [self userDefaults];
617
618  mailAccount = [NSMutableDictionary new];
619
620  // 1. login
621  imapLogin = [[SOGoUserManager sharedUserManager]
622                     getExternalLoginForUID: [self loginInDomain]
623                                   inDomain: [self domain]];
624  [mailAccount setObject: imapLogin forKey: @"userName"];
625
626  // 2. server
627  // imapServer might have the following format
628  // localhost
629  // localhost:143
630  // imap://localhost
631  // imap://localhost:143
632  // imaps://localhost:993
633  // imaps://localhost:143/?tls=YES
634  // imaps://localhost/?tls=YES
635
636  cImapServer = [self _fetchFieldForUser: @"c_imaphostname"];
637  imapServer = [[self domainDefaults] imapServer];
638  cUrl = [NSURL URLWithString: (cImapServer ? cImapServer : @"")];
639  url = [NSURL URLWithString: imapServer];
640  if([cUrl host])
641    imapServer = [cUrl host];
642  else
643    if(cImapServer)
644      imapServer = cImapServer;
645    else
646      if([url host])
647        imapServer = [url host];
648  [mailAccount setObject: imapServer forKey: @"serverName"];
649
650  // 3. port & encryption
651  scheme = [cUrl scheme] ? [cUrl scheme] : [url scheme];
652  query = [cUrl query] ? [cUrl query] : [url query];
653
654  if (scheme
655      && [scheme caseInsensitiveCompare: @"imaps"] == NSOrderedSame)
656    {
657      if (query && [query caseInsensitiveCompare: @"tls=YES"] == NSOrderedSame)
658	{
659	  defaultPort = 143;
660	  encryption = @"tls";
661	}
662      else
663	{
664	  encryption = @"ssl";
665	  defaultPort = 993;
666	}
667    }
668  else
669    {
670      if (query && [query caseInsensitiveCompare: @"tls=YES"] == NSOrderedSame)
671        encryption = @"tls";
672      else
673        encryption = @"none";
674
675      defaultPort = 143;
676    }
677  port = [cUrl port] ? [cUrl port] : [url port];
678  if ([port intValue] == 0) /* port is nil or intValue == 0 */
679    port = [NSNumber numberWithInt: defaultPort];
680  [mailAccount setObject: port forKey: @"port"];
681  [mailAccount setObject: encryption forKey: @"encryption"];
682
683  // Sieve server
684  sieveServer = [self _fetchFieldForUser: @"c_sievehostname"];
685
686  if (sieveServer)
687    {
688      [mailAccount setObject: sieveServer  forKey: @"sieveServerName"];
689    }
690
691  // Identities
692  defaultEmail = [NSString stringWithFormat: @"%@@%@", [self loginInDomain], [self domain]];
693  default_identity = 0;
694  identities = [NSMutableArray new];
695  mails = [NSMutableArray arrayWithArray: [self allEmails]];
696  [mailAccount setObject: [mails objectAtIndex: 0] forKey: @"name"];
697
698  replyTo = [_defaults mailReplyTo];
699
700  max = [mails count];
701
702  /* custom from */
703  if ([[self domainDefaults] mailCustomFromEnabled])
704    {
705      [self userDefaults];
706      customEmail = [_defaults mailCustomEmail];
707      fullName = [_defaults mailCustomFullName];
708      if ([customEmail length] > 0 || [fullName length] > 0)
709        {
710          if ([customEmail length] == 0)
711            customEmail = [mails objectAtIndex: 0];
712          else if ([fullName length] == 0)
713            {
714              // Custom email but default fullname; if the custom email is
715              // one of the user's emails, remove the duplicated entry
716              index = [mails indexOfObject: customEmail];
717              if (index != NSNotFound)
718                {
719                  [mails removeObjectAtIndex: index];
720                  max--;
721                }
722            }
723
724          if ([fullName length] == 0)
725            {
726              fullName = [self cn];
727              if ([fullName length] == 0)
728                fullName = login;
729            }
730
731          identity = [NSMutableDictionary new];
732          [identity setObject: customEmail forKey: @"email"];
733          [identity setObject: fullName forKey: @"fullName"];
734
735          if ([replyTo length] > 0)
736            [identity setObject: replyTo forKey: @"replyTo"];
737
738          signature = [_defaults mailSignature];
739          if (signature)
740            [identity setObject: signature forKey: @"signature"];
741          [identities addObject: identity];
742
743          if ([[identity objectForKey: @"email"] caseInsensitiveCompare: defaultEmail] == NSOrderedSame)
744            default_identity = [identities count]-1;
745
746          [identity release];
747        }
748    }
749
750  for (count = 0; count < max; count++)
751    {
752      identity = [NSMutableDictionary new];
753      fullName = [self cn];
754      if (![fullName length])
755        fullName = login;
756      [identity setObject: fullName forKey: @"fullName"];
757      [identity setObject: [[mails objectAtIndex: count] stringByTrimmingSpaces]
758                   forKey: @"email"];
759
760      if ([replyTo length] > 0)
761        [identity setObject: replyTo forKey: @"replyTo"];
762
763      signature = [_defaults mailSignature];
764      if (signature)
765        [identity setObject: signature forKey: @"signature"];
766      [identities addObject: identity];
767
768      if ([[identity objectForKey: @"email"] caseInsensitiveCompare: defaultEmail] == NSOrderedSame)
769        default_identity = [identities count]-1;
770
771      [identity release];
772    }
773  [[identities objectAtIndex: default_identity] setObject: [NSNumber numberWithBool: YES]
774                                                   forKey: @"isDefault"];
775
776  [mailAccount setObject: identities forKey: @"identities"];
777  [identities release];
778
779  /* receipts */
780  if ([_defaults allowUserReceipt])
781    {
782      receipts = [NSMutableDictionary new];
783
784      [receipts setObject: @"allow" forKey: @"receiptAction"];
785      action = [_defaults userReceiptNonRecipientAction];
786      if (action)
787        [receipts setObject: action forKey: @"receiptNonRecipientAction"];
788      action = [_defaults userReceiptOutsideDomainAction];
789      if (action)
790        [receipts setObject: action forKey: @"receiptOutsideDomainAction"];
791      action = [_defaults userReceiptAnyAction];
792      if (action)
793        [receipts setObject: action forKey: @"receiptAnyAction"];
794
795      [mailAccount setObject: receipts forKey: @"receipts"];
796      [receipts release];
797    }
798
799  /* mailboxes */
800  mailboxes = [NSMutableDictionary new];
801
802  [self _migrateFolderSettings];
803  [mailboxes setObject: [_defaults draftsFolderName]
804                forKey: @"Drafts"];
805  [mailboxes setObject: [_defaults sentFolderName]
806                forKey: @"Sent"];
807  [mailboxes setObject: [_defaults trashFolderName]
808                forKey: @"Trash"];
809  [mailboxes setObject: [_defaults junkFolderName]
810                forKey: @"Junk"];
811  [mailAccount setObject: mailboxes forKey: @"mailboxes"];
812  [mailboxes release];
813
814  [mailAccounts addObject: mailAccount];
815  [mailAccount release];
816}
817
818- (NSArray *) mailAccounts
819{
820  NSArray *auxAccounts;
821
822  if (!mailAccounts)
823    {
824      mailAccounts = [NSMutableArray new];
825      [self _appendSystemMailAccount];
826      if ([[self domainDefaults] mailAuxiliaryUserAccountsEnabled])
827        {
828          auxAccounts = [[self userDefaults] auxiliaryMailAccounts];
829          if (auxAccounts)
830            [mailAccounts addObjectsFromArray: auxAccounts];
831        }
832    }
833
834  return mailAccounts;
835}
836
837- (NSDictionary *) accountWithName: (NSString *) accountName;
838{
839  NSEnumerator *accounts;
840  NSDictionary *mailAccount, *currentAccount;
841
842  mailAccount = nil;
843
844  accounts = [[self mailAccounts] objectEnumerator];
845  while (!mailAccount
846	 && ((currentAccount = [accounts nextObject])))
847    if ([[currentAccount objectForKey: @"name"]
848	  isEqualToString: accountName])
849      mailAccount = currentAccount;
850
851  return mailAccount;
852}
853
854- (NSArray *) allIdentities
855{
856  NSArray *identities;
857
858  identities = [[self mailAccounts] objectsForKey: @"identities"
859                                   notFoundMarker: nil];
860
861  return [identities flattenedArray];
862}
863
864- (NSDictionary *) primaryIdentity
865{
866  NSDictionary *defaultAccount;
867
868  defaultAccount = [[self mailAccounts] objectAtIndex: 0];
869
870  return [[defaultAccount objectForKey: @"identities"] objectAtIndex: 0];
871}
872
873/* folders */
874
875// TODO: those methods should check whether the traversal stack in the context
876//       already contains proper folders to improve caching behaviour
877
878- (SOGoUserFolder *) homeFolderInContext: (id) context
879{
880  return [SOGoUserFolder objectWithName: login
881                            inContainer: [WOApplication application]];
882}
883
884- (SOGoAppointmentFolders *) calendarsFolderInContext: (WOContext *) context
885{
886  return [[self homeFolderInContext: context] lookupName: @"Calendar"
887                                               inContext: context
888                                                 acquire: NO];
889}
890
891- (SOGoAppointmentFolder *) personalCalendarFolderInContext: (WOContext *) context
892{
893  return [[self calendarsFolderInContext: context] lookupPersonalFolder: @"personal"
894                                                         ignoringRights: YES];
895}
896
897- (SOGoContactFolder *) personalContactsFolderInContext: (WOContext *) context
898{
899  SOGoContactFolders *folders;
900
901  folders = [[self homeFolderInContext: context] lookupName: @"Contacts"
902                                                  inContext: context
903                                                    acquire: NO];
904
905  return [folders lookupPersonalFolder: @"personal"
906                        ignoringRights: YES];
907}
908
909
910- (NSArray *) rolesForObject: (NSObject *) object
911                   inContext: (WOContext *) context
912{
913  NSMutableArray *rolesForObject;
914  NSArray *sogoRoles;
915  NSString *rqMethod;
916
917  rolesForObject = [NSMutableArray array];
918
919  sogoRoles = [super rolesForObject: object inContext: context];
920  if (sogoRoles)
921    [rolesForObject addObjectsFromArray: sogoRoles];
922
923  if ([self isSuperUser]
924      || [[object ownerInContext: context] isEqualToString: login])
925    [rolesForObject addObject: SoRole_Owner];
926  else if ([object isKindOfClass: [SOGoObject class]])
927    {
928      sogoRoles = [(SOGoObject *) object aclsForUser: login];
929      if ([sogoRoles count])
930        [rolesForObject addObjectsFromArray: sogoRoles];
931      sogoRoles = [(SOGoObject *) object subscriptionRoles];
932      if ([sogoRoles firstObjectCommonWithArray: rolesForObject])
933	[rolesForObject addObject: SOGoRole_AuthorizedSubscriber];
934      if ([login isEqualToString: @"anonymous"]
935          && [(SOGoObject *) object isInPublicZone])
936        [rolesForObject addObject: SOGoRole_PublicUser];
937    }
938
939#warning this is a hack to work-around the poor implementation of PROPPATCH in SOPE
940  rqMethod = [[context request] method];
941  if ([rqMethod isEqualToString: @"PROPPATCH"])
942    [rolesForObject addObject: @"PROPPATCHer"];
943
944  return rolesForObject;
945}
946
947- (BOOL) isEqual: (id) otherUser
948{
949  return ([otherUser isKindOfClass: [SoUser class]]
950	  && [login isEqualToString: [otherUser login]]);
951}
952
953- (BOOL) isSuperUser
954{
955  [self domainDefaults];
956
957  return [[_domainDefaults superUsernames] containsObject: login];
958}
959
960- (BOOL) canAuthenticate
961{
962  id authValue;
963
964  authValue = [self _fetchFieldForUser: @"canAuthenticate"];
965
966  return [authValue boolValue];
967}
968
969/* resource */
970- (BOOL) isResource
971{
972  NSNumber *v;
973
974  v = [self _fetchFieldForUser: @"isResource"];
975
976  return (v && [v intValue]);
977}
978
979- (int) numberOfSimultaneousBookings
980{
981  NSNumber *v;
982
983  v = [self _fetchFieldForUser: @"numberOfSimultaneousBookings"];
984
985  if (v)
986    return [v intValue];
987
988  return 0;
989}
990
991/* module access */
992- (BOOL) canAccessModule: (NSString *) module
993{
994  id accessValue;
995
996  accessValue = [self _fetchFieldForUser:
997			[NSString stringWithFormat: @"%@Access", module]];
998
999  return [accessValue boolValue];
1000}
1001
1002@end /* SOGoUser */
1003