sogo/SoObjects/SOGo/SOGoUser.m
Francis Lachapelle 208ee08960 feat(mail): handle multiple mail identities
Create read-only identities for email addresses for which no identity has been defined. This way, when visiting the message editor, the user will have access to all of her known email addresses.

Fixes #768, fixes #4602
2020-07-10 17:01:26 -04:00

1182 lines
34 KiB
Objective-C

/*
Copyright (C) 2006-2020 Inverse inc.
This file is part of SOGo.
SOGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
SOGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
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.
*/
#import <NGObjWeb/WOApplication.h>
#import <NGObjWeb/WOContext.h>
#import <NGObjWeb/WORequest.h>
#import <NGExtensions/NSCalendarDate+misc.h>
#import <NGExtensions/NSNull+misc.h>
#import <NGExtensions/NSObject+Logs.h>
#import <Appointments/SOGoAppointmentFolders.h>
#import <Contacts/SOGoContactFolders.h>
#import "NSArray+Utilities.h"
#import "SOGoCache.h"
#import "SOGoDateFormatter.h"
#import "SOGoPermissions.h"
#import "SOGoSystemDefaults.h"
#import "SOGoUserFolder.h"
#import "SOGoUserManager.h"
#import "SOGoUserSettings.h"
#import "WOResourceManager+SOGo.h"
#if defined(MFA_CONFIG)
#include <liboath/oath.h>
#endif
#import "SOGoUser.h"
@implementation SoUser (SOGoExtension)
- (SOGoDomainDefaults *) userDefaults
{
return [SOGoSystemDefaults sharedSystemDefaults];
}
- (SOGoDomainDefaults *) domainDefaults
{
return [SOGoSystemDefaults sharedSystemDefaults];
}
@end
@implementation SOGoUser
// + (NSString *) language
// {
// NSArray *bLanguages;
// WOContext *context;
// NSString *lng;
// context = [[WOApplication application] context];
// bLanguages = [[context request] browserLanguages];
// if ([bLanguages count] > 0)
// lng = [bLanguages objectAtIndex: 0];
// if (![lng length])
// lng = defaultLanguage;
// return lng;
// }
+ (SOGoUser *) userWithLogin: (NSString *) newLogin
{
return [self userWithLogin: newLogin roles: nil];
}
+ (SOGoUser *) userWithLogin: (NSString *) newLogin
roles: (NSArray *) newRoles
{
return [self userWithLogin: newLogin roles: newRoles trust: NO];
}
+ (SOGoUser *) userWithLogin: (NSString *) newLogin
roles: (NSArray *) newRoles
trust: (BOOL) b
{
SOGoCache *cache;
SOGoUser *user;
cache = [SOGoCache sharedCache];
user = [cache userNamed: newLogin];
if (!user)
{
user = [[self alloc] initWithLogin: newLogin roles: newRoles trust: b];
if (user)
{
[cache registerUser: user withName: newLogin];
[user release];
}
}
if (newRoles)
[user setPrimaryRoles: newRoles];
return user;
}
/**
* Return a new instance for the login name, which can be appended by a
* domain name. The domain is extracted only if the system defaults
* SOGoEnableDomainBasedUID is enabled.
*
* @param newLogin a login name optionally follow by @domain
* @param newRoles
* @param b is set to YES if newLogin can be trust
* @see loginInDomain
* @see [SOGoSession decodeValue:usingKey:login:domain:password:]
*/
- (id) initWithLogin: (NSString *) newLogin
roles: (NSArray *) newRoles
trust: (BOOL) b
{
SOGoUserManager *um;
SOGoSystemDefaults *sd;
NSDictionary *contactInfos;
NSString *realUID, *uid, *domain;
NSRange r;
_defaults = nil;
_settings = nil;
uid = nil;
realUID = nil;
domain = nil;
if ([newLogin isEqualToString: @"anonymous"]
|| [newLogin isEqualToString: @"freebusy"])
realUID = newLogin;
else
{
sd = [SOGoSystemDefaults sharedSystemDefaults];
if ([sd enableDomainBasedUID] || [[sd loginDomains] count] > 0)
{
r = [newLogin rangeOfString: @"@" options: NSBackwardsSearch];
if (r.location != NSNotFound)
{
// The domain is probably appended to the username;
// make sure it is defined as a domain in the configuration.
domain = [newLogin substringFromIndex: (r.location + r.length)];
if ([[SOGoUserManager sharedUserManager] isDomainDefined: domain] &&
![sd enableDomainBasedUID])
newLogin = [newLogin substringToIndex: r.location];
if (domain != nil && ![sd enableDomainBasedUID])
// Login domains are enabled (SOGoLoginDomains) but not
// domain-based UID (SOGoEnableDomainBasedUID).
// Drop the domain from the login name.
domain = nil;
}
}
newLogin = [newLogin stringByReplacingString: @"%40"
withString: @"@"];
if (b)
realUID = newLogin;
else
{
um = [SOGoUserManager sharedUserManager];
contactInfos = [um contactInfosForUserWithUIDorEmail: newLogin
inDomain: domain];
realUID = [contactInfos objectForKey: @"c_uid"];
if (domain == nil && [sd enableDomainBasedUID])
domain = [contactInfos objectForKey: @"c_domain"];
}
if ([realUID length] && [domain length])
{
// When the user is associated to a domain, the [SOGoUser login]
// method returns the combination login@domain while
// [SOGoUser loginInDomain] only returns the login.
r = [realUID rangeOfString: domain options: NSBackwardsSearch|NSCaseInsensitiveSearch];
// Do NOT strip @domain.com if SOGoEnableDomainBasedUID is enabled since
// the real login most likely is the email address.
if (r.location != NSNotFound && ![sd enableDomainBasedUID])
uid = [realUID substringToIndex: r.location-1];
// If we don't have the domain in the UID but SOGoEnableDomainBasedUID is
// enabled, let's add it internally so so it becomes unique across
// all potential domains.
else if (r.location == NSNotFound && [sd enableDomainBasedUID])
{
uid = [NSString stringWithString: realUID];
realUID = [NSString stringWithFormat: @"%@@%@", realUID, domain];
}
// We found the domain and SOGoEnableDomainBasedUID is enabled,
// we keep realUID.. This would happen for example if the user
// authenticates with foo@bar.com and the UIDFieldName is also foo@bar.com
else if ([sd enableDomainBasedUID])
uid = [NSString stringWithString: realUID];
}
}
if ([realUID length])
{
if ((self = [super initWithLogin: realUID roles: newRoles]))
{
allEmails = nil;
currentPassword = nil;
cn = nil;
ASSIGN (loginInDomain, (uid ? uid : realUID));
_defaults = nil;
_domainDefaults = nil;
_settings = nil;
mailAccounts = nil;
}
}
else
{
[self release];
self = nil;
}
return self;
}
- (void) dealloc
{
[_defaults release];
[_domainDefaults release];
[_settings release];
[allEmails release];
[mailAccounts release];
[currentPassword release];
[cn release];
[loginInDomain release];
[super dealloc];
}
- (void) setPrimaryRoles: (NSArray *) newRoles
{
ASSIGN (roles, newRoles);
}
- (void) setCurrentPassword: (NSString *) newPassword
{
ASSIGN (currentPassword, newPassword);
}
- (NSString *) currentPassword
{
return currentPassword;
}
- (NSString *) loginInDomain
{
return loginInDomain;
}
- (id) _fetchFieldForUser: (NSString *) field
{
NSDictionary *contactInfos;
SOGoUserManager *um;
um = [SOGoUserManager sharedUserManager];
contactInfos = [um contactInfosForUserWithUIDorEmail: login];
return [contactInfos objectForKey: field];
}
- (void) _fetchAllEmails
{
allEmails = [self _fetchFieldForUser: @"emails"];
[allEmails retain];
}
- (void) _fetchCN
{
cn = [[self _fetchFieldForUser: @"cn"] stringByTrimmingSpaces];
[cn retain];
}
/* properties */
- (NSString *) domain
{
return [self _fetchFieldForUser: @"c_domain"];
}
- (id <SOGoSource>) authenticationSource
{
NSString *sourceID;
SOGoUserManager *um;
sourceID = [self _fetchFieldForUser: @"SOGoSource"];
um = [SOGoUserManager sharedUserManager];
return [um sourceWithID: sourceID];
}
- (NSArray *) allEmails
{
if (!allEmails)
[self _fetchAllEmails];
return allEmails;
}
//
// We always return the last object among our list of email addresses. This value
// is always added in SOGoUserManager: -_fillContactMailRecords:
//
- (NSString *) systemEmail
{
if (!allEmails)
[self _fetchAllEmails];
return [allEmails objectAtIndex: 0];
}
- (BOOL) hasEmail: (NSString *) email
{
if (!allEmails)
[self _fetchAllEmails];
return [allEmails containsCaseInsensitiveString: email];
}
- (NSString *) cn
{
if (!cn)
[self _fetchCN];
return cn;
}
- (NSMutableDictionary *) defaultIdentity
{
NSDictionary *defaultAccount, *currentIdentity;
NSMutableDictionary *defaultIdentity;
NSArray *identities;
NSString *defaultEmail;
unsigned int count, max;
defaultEmail = [NSString stringWithFormat: @"%@@%@", [self loginInDomain], [self domain]];
defaultAccount = [[self mailAccounts] objectAtIndex: 0];
defaultIdentity = nil;
identities = [defaultAccount objectForKey: @"identities"];
max = [identities count];
for (count = 0; count < max; count++)
{
currentIdentity = [identities objectAtIndex: count];
if ([[currentIdentity objectForKey: @"isDefault"] boolValue])
{
defaultIdentity = [NSMutableDictionary dictionaryWithDictionary: currentIdentity];
break;
}
else if ([[currentIdentity objectForKey: @"email"] caseInsensitiveCompare: defaultEmail] == NSOrderedSame)
{
defaultIdentity = [NSMutableDictionary dictionaryWithDictionary: currentIdentity];
}
}
return defaultIdentity; // can be nil
}
- (SOGoDateFormatter *) dateFormatterInContext: (WOContext *) context
{
SOGoDateFormatter *dateFormatter;
NSString *format;
SOGoUserDefaults *ud;
NSDictionary *locale;
WOResourceManager *resMgr;
dateFormatter = [SOGoDateFormatter new];
[dateFormatter autorelease];
ud = [self userDefaults];
resMgr = [[WOApplication application] resourceManager];
locale = [resMgr localeForLanguageNamed: [ud language]];
[dateFormatter setLocale: locale];
format = [ud shortDateFormat];
if (format)
[dateFormatter setShortDateFormat: format];
format = [ud longDateFormat];
if (format)
[dateFormatter setLongDateFormat: format];
format = [ud timeFormat];
if (format)
[dateFormatter setTimeFormat: format];
return dateFormatter;
}
- (NSDictionary *) currentDay
{
NSCalendarDate *now;
NSDictionary *description, *abbr;
NSDictionary *locale;
SOGoUserDefaults *ud;
WOResourceManager *resMgr;
NSUInteger seconds;
now = [NSCalendarDate calendarDate];
ud = [self userDefaults];
resMgr = [[WOApplication application] resourceManager];
locale = [resMgr localeForLanguageNamed: [ud language]];
[now setTimeZone: [ud timeZone]];
seconds = [now hourOfDay]*3600 + [now minuteOfHour]*60 + [now secondOfMinute];
abbr = [NSDictionary dictionaryWithObjectsAndKeys:
[now descriptionWithCalendarFormat: @"%a" locale: locale], @"weekday",
[now descriptionWithCalendarFormat: @"%b" locale: locale], @"month",
nil];
description = [NSDictionary dictionaryWithObjectsAndKeys:
[now descriptionWithCalendarFormat: @"%A" locale: locale], @"weekday",
[now descriptionWithCalendarFormat: @"%B" locale: locale], @"month",
[now descriptionWithCalendarFormat: @"%d" locale: locale], @"day",
[now descriptionWithCalendarFormat: @"%Y" locale: locale], @"year",
abbr, @"abbr",
[NSNumber numberWithInt: (24*3600 - seconds)], @"secondsBeforeTomorrow",
nil];
return description;
}
- (SOGoUserDefaults *) userDefaults
{
if (!_defaults)
{
_defaults = [SOGoUserDefaults defaultsForUser: login
inDomain: [self domain]];
[_defaults retain];
}
//else
// NSLog(@"User defaults cache hit for %@", login);
return _defaults;
}
- (SOGoDomainDefaults *) domainDefaults
{
NSString *domain;
if (!_domainDefaults)
{
domain = [self domain];
if ([domain length])
{
_domainDefaults = [SOGoDomainDefaults defaultsForDomain: domain];
if (!_domainDefaults)
{
//[self errorWithFormat: @"domain '%@' does not exist!", domain];
_domainDefaults = [SOGoSystemDefaults sharedSystemDefaults];
}
}
else
_domainDefaults = [SOGoSystemDefaults sharedSystemDefaults];
[_domainDefaults retain];
}
//else
// NSLog(@"User defaults cache hit for %@", login);
return _domainDefaults;
}
- (SOGoUserSettings *) userSettings
{
if (!_settings)
{
_settings = [SOGoUserSettings settingsForUser: login];
[_settings retain];
}
return _settings;
}
- (NSCalendarDate *) firstDayOfWeekForDate: (NSCalendarDate *) date
{
int offset;
NSCalendarDate *firstDay;
offset = [[self userDefaults] firstDayOfWeek] - [date dayOfWeek];
if (offset > 0)
offset -= 7;
firstDay = [date addTimeInterval: offset * 86400];
return firstDay;
}
- (unsigned int) dayOfWeekForDate: (NSCalendarDate *) date
{
unsigned int offset, baseDayOfWeek, dayOfWeek;
offset = [[self userDefaults] firstDayOfWeek];
baseDayOfWeek = [date dayOfWeek];
if (offset > baseDayOfWeek)
baseDayOfWeek += 7;
dayOfWeek = baseDayOfWeek - offset;
return dayOfWeek;
}
- (NSCalendarDate *) firstWeekOfYearForDate: (NSCalendarDate *) date
{
NSString *firstWeekRule;
NSCalendarDate *januaryFirst, *firstWeek;
unsigned int dayOfWeek;
firstWeekRule = [[self userDefaults] firstWeekOfYear];
januaryFirst = [NSCalendarDate dateWithYear: [date yearOfCommonEra]
month: 1 day: 1 hour: 0 minute: 0 second: 0
timeZone: [date timeZone]];
if ([firstWeekRule isEqualToString: SOGoWeekStartFirst4DayWeek])
{
dayOfWeek = [self dayOfWeekForDate: januaryFirst];
if (dayOfWeek < 4)
firstWeek = [self firstDayOfWeekForDate: januaryFirst];
else
firstWeek = [self firstDayOfWeekForDate: [januaryFirst
dateByAddingYears: 0
months: 0
days: 7]];
}
else if ([firstWeekRule isEqualToString: SOGoWeekStartFirstFullWeek])
{
dayOfWeek = [self dayOfWeekForDate: januaryFirst];
if (dayOfWeek == 0)
firstWeek = [self firstDayOfWeekForDate: januaryFirst];
else
firstWeek = [self firstDayOfWeekForDate: [januaryFirst
dateByAddingYears: 0
months: 0
days: 7]];
}
else
firstWeek = [self firstDayOfWeekForDate: januaryFirst];
return firstWeek;
}
- (unsigned int) weekNumberForDate: (NSCalendarDate *) date
{
NSCalendarDate *firstWeek, *previousWeek;
unsigned int weekNumber;
firstWeek = [self firstWeekOfYearForDate: date];
if ([firstWeek earlierDate: date] == firstWeek)
{
weekNumber = ([date timeIntervalSinceDate: firstWeek] / (86400 * 7) + 1);
}
else
{
// Date is within the last week of the previous year;
// Compute the previous week number to find the week number of the requested date.
// The number will either be 52 or 53.
previousWeek = [date dateByAddingYears: 0
months: 0
days: -7];
firstWeek = [self firstWeekOfYearForDate: previousWeek];
weekNumber = ([previousWeek timeIntervalSinceDate: firstWeek] / (86400 * 7) + 1);
weekNumber += 1;
}
return weekNumber;
}
/* mail */
- (BOOL) _migrateFolderWithPurpose: (NSString *) purpose
withName: (NSString *) folderName
{
NSString *methodName;
SEL methodSel;
BOOL rc;
[self userDefaults];
methodName = [NSString stringWithFormat: @"set%@FolderName:", purpose];
methodSel = NSSelectorFromString (methodName);
if ([_defaults respondsToSelector: methodSel])
{
[_defaults performSelector: methodSel withObject: folderName];
rc = YES;
}
else
{
[self errorWithFormat: @"method '%@' not available with user defaults"
@" object, folder migration fails", methodName];
rc = NO;
}
return rc;
}
- (void) _migrateFolderSettings
{
NSMutableDictionary *mailSettings;
NSString *folderName, *key;
BOOL migrated;
NSString **purpose;
NSString *purposes[] = { @"Drafts", @"Sent", @"Trash", nil };
[self userSettings];
mailSettings = [_settings objectForKey: @"Mail"];
if (mailSettings)
{
migrated = NO;
purpose = purposes;
while (*purpose)
{
key = [NSString stringWithFormat: @"%@Folder", *purpose];
folderName = [mailSettings objectForKey: key];
if ([folderName length]
&& [self _migrateFolderWithPurpose: *purpose
withName: folderName])
{
migrated = YES;
[mailSettings removeObjectForKey: key];
folderName = nil;
}
purpose++;
}
if (migrated)
{
[_settings synchronize];
[self userDefaults];
[_defaults synchronize];
}
}
}
- (void) _appendSystemMailAccountWithDelegatedIdentities: (BOOL) appendDeletegatedIdentities
{
NSString *fullName, *imapLogin, *imapServer, *cImapServer,
*encryption, *scheme, *action, *query, *customEmail, *sieveServer;
NSMutableDictionary *mailAccount, *identity, *mailboxes, *receipts, *security, *mailSettings;
NSNumber *port;
NSMutableArray *identities, *mails;
NSArray *delegators, *delegates;
NSURL *url, *cUrl;
unsigned int count, max; //, default_identity;
NSInteger defaultPort;
NSUInteger index;
BOOL hasDefaultIdentity;
[self userDefaults]; // set _defaults
[self userSettings]; // set _settings
mailSettings = [_settings objectForKey: @"Mail"];
mailAccount = [NSMutableDictionary new];
// 1. login
imapLogin = [[SOGoUserManager sharedUserManager]
getExternalLoginForUID: [self loginInDomain]
inDomain: [self domain]];
[mailAccount setObject: imapLogin forKey: @"userName"];
// 2. server
// imapServer might have the following format
// localhost
// localhost:143
// imap://localhost
// imap://localhost:143
// imaps://localhost:993
// imaps://localhost:143/?tls=YES
// imaps://localhost/?tls=YES
cImapServer = [self _fetchFieldForUser: @"c_imaphostname"];
imapServer = [[self domainDefaults] imapServer];
cUrl = [NSURL URLWithString: (cImapServer ? cImapServer : @"")];
url = [NSURL URLWithString: imapServer];
if([cUrl host])
imapServer = [cUrl host];
else
if(cImapServer)
imapServer = cImapServer;
else
if([url host])
imapServer = [url host];
[mailAccount setObject: imapServer forKey: @"serverName"];
// 3. port & encryption
scheme = [cUrl scheme] ? [cUrl scheme] : [url scheme];
query = [cUrl query] ? [cUrl query] : [url query];
if (scheme
&& [scheme caseInsensitiveCompare: @"imaps"] == NSOrderedSame)
{
if (query && [query caseInsensitiveCompare: @"tls=YES"] == NSOrderedSame)
{
defaultPort = 143;
encryption = @"tls";
}
else
{
encryption = @"ssl";
defaultPort = 993;
}
}
else
{
if (query && [query caseInsensitiveCompare: @"tls=YES"] == NSOrderedSame)
encryption = @"tls";
else
encryption = @"none";
defaultPort = 143;
}
port = [cUrl port] ? [cUrl port] : [url port];
if ([port intValue] == 0) /* port is nil or intValue == 0 */
port = [NSNumber numberWithInt: defaultPort];
[mailAccount setObject: port forKey: @"port"];
[mailAccount setObject: encryption forKey: @"encryption"];
// 4. Sieve server
sieveServer = [self _fetchFieldForUser: @"c_sievehostname"];
if (sieveServer)
{
[mailAccount setObject: sieveServer forKey: @"sieveServerName"];
}
// 5. Identities
identities = [NSMutableArray new];
[identities addObjectsFromArray: [_defaults mailIdentities]];
mails = [NSMutableArray arrayWithArray: [self allEmails]];
[mailAccount setObject: [mails objectAtIndex: 0] forKey: @"name"];
max = [identities count];
hasDefaultIdentity = NO;
fullName = [self cn];
if ([fullName length] == 0)
fullName = login;
// Sanitize identities
for (count = 0; count < max; count++)
{
identity = [NSMutableDictionary dictionaryWithDictionary: [identities objectAtIndex: count]];
customEmail = [identity objectForKey: @"email"];
if ([customEmail length])
{
if (![[self domainDefaults] mailCustomFromEnabled])
{
// No custom from -- enforce a valid email
index = [mails indexOfObject: customEmail];
if (index == NSNotFound)
{
[identity setObject: [self systemEmail] forKey: @"email"];
}
}
}
else
{
// Email must be defined
[identity setObject: [self systemEmail] forKey: @"email"];
}
if (![[self domainDefaults] mailCustomFromEnabled])
{
// No custom from -- enforce a valid fullname and remove reply-to
[identity setObject: fullName forKey: @"fullName"];
[identity removeObjectForKey: @"replyTo"];
}
if (!appendDeletegatedIdentities)
{
[identity setObject: [NSNumber numberWithBool: YES] forKey: @"isReadOnly"];
}
if ([[identity objectForKey: @"isDefault"] boolValue])
{
if (hasDefaultIdentity || !appendDeletegatedIdentities)
[identity removeObjectForKey: @"isDefault"]; // only one possible default identity
else
hasDefaultIdentity = YES;
}
[identities replaceObjectAtIndex: count withObject: identity];
}
// Create an identity for each missing email address
max = [mails count];
for (count = 0; count < max; count++)
{
BOOL noIdentityForEmail;
NSString *identityEmail;
noIdentityForEmail = YES;
customEmail = [mails objectAtIndex: count];
for (index = 0; noIdentityForEmail && index < [identities count]; index++)
{
identity = [identities objectAtIndex: index];
identityEmail = [identity objectForKey: @"email"];
if ([customEmail caseInsensitiveCompare: identityEmail] == NSOrderedSame)
{
noIdentityForEmail = NO;
}
}
if (noIdentityForEmail)
{
identity = [NSMutableDictionary dictionaryWithObjectsAndKeys:
fullName, @"fullName",
customEmail, @"email", nil];
if (appendDeletegatedIdentities &&
count == 0 &&
[identities count] == 0)
{
// First identity uses the system email -- mark it as the default and keep it editable
[identity setObject: [NSNumber numberWithBool: YES] forKey: @"isDefault"];
hasDefaultIdentity = YES;
}
else
{
// This additional identity should not appear in the identity manager of the Preferences
[identity setObject: [NSNumber numberWithBool: YES] forKey: @"isReadOnly"];
}
[identities addObject: identity];
}
}
/* identities from delegators */
if (appendDeletegatedIdentities)
{
delegators = [mailSettings objectForKey: @"DelegateFrom"];
if (delegators)
{
BOOL dirty;
NSDictionary *delegatorAccount, *delegatorSettings;
NSMutableArray *validDelegators;
NSString *delegatorLogin;
SOGoUser *delegatorUser;
dirty = NO;
validDelegators = [NSMutableArray array];
max = [delegators count];
for (count = 0; count < max; count++)
{
// 1. Verify if delegator is valid
delegatorLogin = [delegators objectAtIndex: count];
delegatorUser = [SOGoUser userWithLogin: delegatorLogin];
if (delegatorUser)
{
// 2. Verify if delegator still delegates to user
delegatorSettings = [[delegatorUser userSettings] objectForKey: @"Mail"];
delegates = [delegatorSettings objectForKey: @"DelegateTo"];
if ([delegates containsObject: [self login]])
{
[validDelegators addObject: delegatorLogin];
delegatorAccount = [[delegatorUser mailAccountsWithDelegatedIdentities: NO] objectAtIndex: 0];
[identities addObjectsFromArray: [delegatorAccount objectForKey: @"identities"]];
}
else
dirty = YES;
}
else
dirty = YES;
}
if (dirty)
{
[mailSettings setObject: validDelegators
forKey: @"DelegateFrom"];
[_settings synchronize];
}
}
}
[mailAccount setObject: identities forKey: @"identities"];
[identities release];
// 6. Receipts
if ([_defaults allowUserReceipt])
{
receipts = [NSMutableDictionary new];
[receipts setObject: @"allow" forKey: @"receiptAction"];
action = [_defaults userReceiptNonRecipientAction];
if (action)
[receipts setObject: action forKey: @"receiptNonRecipientAction"];
action = [_defaults userReceiptOutsideDomainAction];
if (action)
[receipts setObject: action forKey: @"receiptOutsideDomainAction"];
action = [_defaults userReceiptAnyAction];
if (action)
[receipts setObject: action forKey: @"receiptAnyAction"];
[mailAccount setObject: receipts forKey: @"receipts"];
[receipts release];
}
// 7. Mailboxes
mailboxes = [NSMutableDictionary new];
[self _migrateFolderSettings];
[mailboxes setObject: [_defaults draftsFolderName]
forKey: @"Drafts"];
[mailboxes setObject: [_defaults sentFolderName]
forKey: @"Sent"];
[mailboxes setObject: [_defaults trashFolderName]
forKey: @"Trash"];
[mailboxes setObject: [_defaults junkFolderName]
forKey: @"Junk"];
[mailAccount setObject: mailboxes forKey: @"specialMailboxes"];
[mailboxes release];
// 8. Delegates
delegates = [mailSettings objectForKey: @"DelegateTo"];
if (!delegates)
delegates = [NSArray array];
else
{
NSMutableArray *allDelegates;
SOGoUser *delegate;
allDelegates = [NSMutableArray array];
for (count = 0; count < [delegates count]; count++)
{
delegate = [SOGoUser userWithLogin: [delegates objectAtIndex: count]];
[allDelegates addObject: [NSDictionary dictionaryWithObjectsAndKeys: [delegates objectAtIndex: count], @"uid",
[delegate cn], @"cn",
[delegate systemEmail], @"c_email", nil]];
}
delegates = allDelegates;
}
[mailAccount setObject: delegates forKey: @"delegates"];
// 9. Security
if ([[_defaults mailCertificate] length])
{
security = [NSMutableDictionary new];
[security setObject: [NSNumber numberWithBool: YES] forKey: @"hasCertificate"];
if ([_defaults mailCertificateAlwaysSign])
[security setObject: [NSNumber numberWithBool: YES] forKey: @"alwaysSign"];
if ([_defaults mailCertificateAlwaysEncrypt])
[security setObject: [NSNumber numberWithBool: YES] forKey: @"alwaysEncrypt"];
[mailAccount setObject: security forKey: @"security"];
[security release];
}
[mailAccounts addObject: mailAccount];
[mailAccount release];
}
- (NSArray *) mailAccounts
{
return [self mailAccountsWithDelegatedIdentities: YES];
}
- (NSArray *) mailAccountsWithDelegatedIdentities: (BOOL) appendDeletegatedIdentities
{
NSArray *auxAccounts;
if (!mailAccounts)
{
mailAccounts = [NSMutableArray new];
[self _appendSystemMailAccountWithDelegatedIdentities: appendDeletegatedIdentities];
if ([[self domainDefaults] mailAuxiliaryUserAccountsEnabled])
{
auxAccounts = [[self userDefaults] auxiliaryMailAccounts];
if (auxAccounts)
[mailAccounts addObjectsFromArray: auxAccounts];
}
}
return mailAccounts;
}
- (NSDictionary *) accountWithName: (NSString *) accountName;
{
NSEnumerator *accounts;
NSDictionary *mailAccount, *currentAccount;
mailAccount = nil;
accounts = [[self mailAccounts] objectEnumerator];
while (!mailAccount
&& ((currentAccount = [accounts nextObject])))
if ([[currentAccount objectForKey: @"name"]
isEqualToString: accountName])
mailAccount = currentAccount;
return mailAccount;
}
- (NSArray *) allIdentities
{
NSArray *identities;
identities = [[self mailAccounts] objectsForKey: @"identities"
notFoundMarker: nil];
return [identities flattenedArray];
}
- (NSDictionary *) primaryIdentity
{
NSArray *identities;
NSDictionary *defaultIdentity, *defaultAccount;
defaultIdentity = [self defaultIdentity];
if (!defaultIdentity && [[self mailAccounts] count])
{
defaultAccount = [[self mailAccounts] objectAtIndex: 0];
identities = [defaultAccount objectForKey: @"identities"];
defaultIdentity = [identities objectAtIndex: 0];
}
return defaultIdentity;
}
/* folders */
// TODO: those methods should check whether the traversal stack in the context
// already contains proper folders to improve caching behaviour
- (SOGoUserFolder *) homeFolderInContext: (id) context
{
return [SOGoUserFolder objectWithName: login
inContainer: [WOApplication application]];
}
- (SOGoAppointmentFolders *) calendarsFolderInContext: (WOContext *) context
{
return [[self homeFolderInContext: context] lookupName: @"Calendar"
inContext: context
acquire: NO];
}
- (SOGoAppointmentFolder *) personalCalendarFolderInContext: (WOContext *) context
{
return [[self calendarsFolderInContext: context] lookupPersonalFolder: @"personal"
ignoringRights: YES];
}
- (SOGoContactFolder *) personalContactsFolderInContext: (WOContext *) context
{
SOGoContactFolders *folders;
folders = [[self homeFolderInContext: context] lookupName: @"Contacts"
inContext: context
acquire: NO];
return [folders lookupPersonalFolder: @"personal"
ignoringRights: YES];
}
- (NSArray *) rolesForObject: (NSObject *) object
inContext: (WOContext *) context
{
NSMutableArray *rolesForObject;
NSArray *sogoRoles;
NSString *rqMethod;
rolesForObject = [NSMutableArray array];
sogoRoles = [super rolesForObject: object inContext: context];
if (sogoRoles)
[rolesForObject addObjectsFromArray: sogoRoles];
if ([self isSuperUser]
|| [[object ownerInContext: context] isEqualToString: login])
[rolesForObject addObject: SoRole_Owner];
else if ([object isKindOfClass: [SOGoObject class]])
{
sogoRoles = [(SOGoObject *) object aclsForUser: login];
if ([sogoRoles count])
[rolesForObject addObjectsFromArray: sogoRoles];
sogoRoles = [(SOGoObject *) object subscriptionRoles];
if ([sogoRoles firstObjectCommonWithArray: rolesForObject])
[rolesForObject addObject: SOGoRole_AuthorizedSubscriber];
if ([login isEqualToString: @"anonymous"]
&& [(SOGoObject *) object isInPublicZone])
[rolesForObject addObject: SOGoRole_PublicUser];
}
#warning this is a hack to work-around the poor implementation of PROPPATCH in SOPE
rqMethod = [[context request] method];
if ([rqMethod isEqualToString: @"PROPPATCH"])
[rolesForObject addObject: @"PROPPATCHer"];
return rolesForObject;
}
- (BOOL) isEqual: (id) otherUser
{
return ([otherUser isKindOfClass: [SoUser class]]
&& [login isEqualToString: [otherUser login]]);
}
- (BOOL) isSuperUser
{
[self domainDefaults];
return [[_domainDefaults superUsernames] containsObject: login];
}
- (BOOL) canAuthenticate
{
id authValue;
authValue = [self _fetchFieldForUser: @"canAuthenticate"];
return [authValue boolValue];
}
- (NSString *) googleAuthenticatorKey
{
#if defined(MFA_CONFIG)
NSString *key, *result;
const char *s;
char *secret;
size_t s_len, secret_len;
key = [[[self userSettings] userSalt] substringToIndex: 12];
s = [key UTF8String];
s_len = strlen(s);
oath_init();
oath_base32_encode(s,s_len, &secret, &secret_len);
oath_done();
result = [[NSString alloc] initWithBytesNoCopy: secret
length: secret_len
encoding: NSASCIIStringEncoding
freeWhenDone: YES];
return [result autorelease];
#else
return nil;
#endif
}
/* resource */
- (BOOL) isResource
{
NSNumber *v;
v = [self _fetchFieldForUser: @"isResource"];
return (v && [v intValue]);
}
- (int) numberOfSimultaneousBookings
{
NSNumber *v;
v = [self _fetchFieldForUser: @"numberOfSimultaneousBookings"];
if (v)
return [v intValue];
return 0;
}
/* module access */
- (BOOL) canAccessModule: (NSString *) module
{
id accessValue;
accessValue = [self _fetchFieldForUser:
[NSString stringWithFormat: @"%@Access", module]];
return [accessValue boolValue];
}
@end /* SOGoUser */