Monotone-Parent: c16aa62954764e28a982189ae9222645f4705921 Monotone-Revision: a46f694fb88b061ebf1cde5157986657756d6f5c Monotone-Author: flachapelle@inverse.ca Monotone-Date: 2011-11-03T11:51:05 Monotone-Branch: ca.inverse.sogo
569 lines
17 KiB
Objective-C
569 lines
17 KiB
Objective-C
/* SOGoToolRenameUser.m - this file is part of SOGo
|
|
*
|
|
* Copyright (C) 2011 Inverse inc
|
|
*
|
|
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
|
*
|
|
* This file is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This file 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
|
|
#import <Foundation/NSAutoreleasePool.h>
|
|
#import <Foundation/NSArray.h>
|
|
#import <Foundation/NSDictionary.h>
|
|
#import <Foundation/NSException.h>
|
|
#import <Foundation/NSString.h>
|
|
|
|
#import <EOControl/EOQualifier.h>
|
|
#import <GDLAccess/EOAdaptorChannel.h>
|
|
#import <GDLAccess/EOAdaptorContext.h>
|
|
|
|
#import <GDLContentStore/GCSChannelManager.h>
|
|
#import <GDLContentStore/GCSFolder.h>
|
|
#import <GDLContentStore/GCSFolderManager.h>
|
|
#import <GDLContentStore/NSURL+GCS.h>
|
|
|
|
#import <SOGo/NSString+Utilities.h>
|
|
#import <SOGo/SOGoSystemDefaults.h>
|
|
#import <SOGo/SOGoUserSettings.h>
|
|
|
|
#import "SOGoTool.h"
|
|
|
|
@interface SOGoToolRenameUser : SOGoTool
|
|
{
|
|
NSString *oldUserID;
|
|
NSString *newUserID;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation SOGoToolRenameUser
|
|
|
|
+ (NSString *) command
|
|
{
|
|
return @"rename-user";
|
|
}
|
|
|
|
+ (NSString *) description
|
|
{
|
|
return @"update records pertaining to a user after a change of user id";
|
|
}
|
|
|
|
- (id) init
|
|
{
|
|
if ((self = [super init]))
|
|
{
|
|
oldUserID = nil;
|
|
newUserID = nil;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
[oldUserID release];
|
|
[newUserID release];
|
|
[super dealloc];
|
|
}
|
|
|
|
- (void) usage
|
|
{
|
|
fprintf (stderr, "rename-user <fromuserid> <touserid>\n\n"
|
|
" fromuserid the previous user id\n"
|
|
" touserid the new user id\n\n"
|
|
"Example: sogo-tool rename-user jane_doe janedoe\n");
|
|
}
|
|
|
|
- (BOOL) parseArguments
|
|
{
|
|
BOOL rc = NO;
|
|
int max;
|
|
|
|
max = [arguments count];
|
|
if (max == 2)
|
|
{
|
|
ASSIGN (oldUserID, [arguments objectAtIndex: 0]);
|
|
ASSIGN (newUserID, [arguments objectAtIndex: 1]);
|
|
rc = YES;
|
|
}
|
|
else
|
|
[self usage];
|
|
|
|
return rc;
|
|
}
|
|
|
|
- (BOOL) _updateSOGoFolderInfoFromUser: (NSString *) fromUserID
|
|
toUser: (NSString *) toUserID
|
|
{
|
|
BOOL rc = NO;
|
|
GCSFolderManager *fm;
|
|
GCSChannelManager *cm;
|
|
NSURL *folderLocation;
|
|
EOAdaptorContext *ac;
|
|
EOAdaptorChannel *fc;
|
|
NSString *sql;
|
|
NSString *sqlFromUserID, *sqlToUserID;
|
|
NSException *sqlError;
|
|
|
|
fm = [GCSFolderManager defaultFolderManager];
|
|
cm = [fm channelManager];
|
|
folderLocation = [fm folderInfoLocation];
|
|
fc = [cm acquireOpenChannelForURL: folderLocation];
|
|
ac = [fc adaptorContext];
|
|
sqlFromUserID = [fromUserID asSafeSQLString];
|
|
sqlToUserID = [toUserID asSafeSQLString];
|
|
|
|
[ac beginTransaction];
|
|
|
|
sql = [NSString stringWithFormat: @"UPDATE %@ SET c_path2 = '%@'"
|
|
@" WHERE c_path2 = '%@'",
|
|
[folderLocation gcsTableName], sqlToUserID, sqlFromUserID];
|
|
sqlError = [fc evaluateExpressionX: sql];
|
|
if (!sqlError)
|
|
{
|
|
sql
|
|
= [NSString stringWithFormat: @"UPDATE %@"
|
|
@" SET c_path = '/'||c_path1||'/'||c_path2||'/'||c_path3||'/'||c_path4"
|
|
@" WHERE c_path2 = '%@'",
|
|
[folderLocation gcsTableName], sqlToUserID];
|
|
sqlError = [fc evaluateExpressionX: sql];
|
|
}
|
|
|
|
if (sqlError)
|
|
[ac rollbackTransaction];
|
|
else
|
|
rc = [ac commitTransaction];
|
|
|
|
[cm releaseChannel: fc];
|
|
|
|
return rc;
|
|
}
|
|
|
|
- (BOOL) _updateSOGoFolderInfo
|
|
{
|
|
return [self _updateSOGoFolderInfoFromUser: oldUserID toUser: newUserID];
|
|
}
|
|
|
|
- (void) _rollbackSOGoFolderInfo
|
|
{
|
|
[self _updateSOGoFolderInfoFromUser: newUserID toUser: oldUserID];
|
|
}
|
|
|
|
- (BOOL) _updateSOGoUserProfileFromUser: (NSString *) fromUserID
|
|
toUser: (NSString *) toUserID
|
|
{
|
|
BOOL rc = NO;
|
|
GCSFolderManager *fm;
|
|
GCSChannelManager *cm;
|
|
NSURL *profileLocation;
|
|
EOAdaptorContext *ac;
|
|
EOAdaptorChannel *fc;
|
|
NSString *profileURL, *sql, *sqlFromUserID, *sqlToUserID;
|
|
NSException *sqlError;
|
|
SOGoSystemDefaults *sd;
|
|
|
|
fm = [GCSFolderManager defaultFolderManager];
|
|
cm = [fm channelManager];
|
|
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
|
profileURL = [sd profileURL];
|
|
profileLocation = [[NSURL alloc] initWithString: profileURL];
|
|
[profileLocation autorelease];
|
|
fc = [cm acquireOpenChannelForURL: profileLocation];
|
|
ac = [fc adaptorContext];
|
|
sqlFromUserID = [fromUserID asSafeSQLString];
|
|
sqlToUserID = [toUserID asSafeSQLString];
|
|
|
|
[ac beginTransaction];
|
|
|
|
sql = [NSString stringWithFormat: @"UPDATE %@ SET c_uid = '%@'"
|
|
@" WHERE c_uid = '%@'",
|
|
[profileLocation gcsTableName], sqlToUserID, sqlFromUserID];
|
|
sqlError = [fc evaluateExpressionX: sql];
|
|
if (sqlError)
|
|
[ac rollbackTransaction];
|
|
else
|
|
rc = [ac commitTransaction];
|
|
|
|
[cm releaseChannel: fc];
|
|
|
|
return rc;
|
|
}
|
|
|
|
- (BOOL) _updateSOGoUserProfile
|
|
{
|
|
return [self _updateSOGoUserProfileFromUser: oldUserID toUser: newUserID];
|
|
}
|
|
|
|
- (void) _rollbackSOGoUserProfile
|
|
{
|
|
[self _updateSOGoUserProfileFromUser: newUserID toUser: oldUserID];
|
|
}
|
|
|
|
- (NSArray *) _fetchSubcribersForUser: (NSString *) fromUserID
|
|
{
|
|
NSMutableArray *subscribers;
|
|
GCSFolderManager *fm;
|
|
GCSChannelManager *cm;
|
|
NSURL *profileLocation;
|
|
EOAdaptorChannel *fc;
|
|
NSString *profileURL, *sql, *sqlFromUserID;
|
|
SOGoSystemDefaults *sd;
|
|
NSArray *attrs;
|
|
NSDictionary *row;
|
|
|
|
subscribers = [NSMutableArray array];
|
|
|
|
fm = [GCSFolderManager defaultFolderManager];
|
|
cm = [fm channelManager];
|
|
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
|
profileURL = [sd profileURL];
|
|
profileLocation = [[NSURL alloc] initWithString: profileURL];
|
|
[profileLocation autorelease];
|
|
fc = [cm acquireOpenChannelForURL: profileLocation];
|
|
sqlFromUserID = [fromUserID asSafeSQLString];
|
|
sql = [NSString stringWithFormat:
|
|
@"SELECT c_uid FROM %@ WHERE c_settings LIKE '%%\"%@:%%'",
|
|
[profileLocation gcsTableName], sqlFromUserID];
|
|
[fc evaluateExpressionX: sql];
|
|
attrs = [fc describeResults: NO];
|
|
while ((row = [fc fetchAttributes: attrs withZone: NULL]))
|
|
[subscribers addObject: [row objectForKey: @"c_uid"]];
|
|
[fc cancelFetch];
|
|
[cm releaseChannel: fc];
|
|
|
|
return subscribers;
|
|
}
|
|
|
|
- (NSArray *) _updateSubscriptionsForConfig: (NSArray *) subscriptions
|
|
fromUser: (NSString *) fromUserID
|
|
toUser: (NSString *) toUserID
|
|
{
|
|
NSMutableArray *newSubscriptions;
|
|
BOOL modified = NO;
|
|
NSString *oldPrefix, *oldSubscription, *newPrefix, *rest;
|
|
NSUInteger count, max;
|
|
|
|
newSubscriptions = [subscriptions mutableCopy];
|
|
[newSubscriptions autorelease];
|
|
oldPrefix = [NSString stringWithFormat: @"%@:", fromUserID];
|
|
newPrefix = [NSString stringWithFormat: @"%@:", toUserID];
|
|
max = [subscriptions count];
|
|
for (count = 0; count < max; count++)
|
|
{
|
|
oldSubscription = [subscriptions objectAtIndex: count];
|
|
if ([oldSubscription hasPrefix: oldPrefix])
|
|
{
|
|
modified = YES;
|
|
rest = [oldSubscription substringFromIndex: [oldPrefix length]];
|
|
[newSubscriptions replaceObjectAtIndex: count
|
|
withObject: [NSString stringWithFormat: @"%@%@", newPrefix, rest]];
|
|
}
|
|
}
|
|
|
|
if (!modified)
|
|
newSubscriptions = nil;
|
|
|
|
return newSubscriptions;
|
|
}
|
|
|
|
- (NSDictionary *) _updatedValuesForConfig: (NSDictionary *) config
|
|
fromUser: (NSString *) fromUserID
|
|
toUser: (NSString *) toUserID
|
|
{
|
|
NSMutableDictionary *newConfig;
|
|
BOOL modified = NO;
|
|
NSString *oldPrefix, *oldKey, *newPrefix, *rest;
|
|
NSArray *keys;
|
|
NSUInteger count, max;
|
|
|
|
newConfig = [config mutableCopy];
|
|
[newConfig autorelease];
|
|
oldPrefix = [NSString stringWithFormat: @"%@:", fromUserID];
|
|
newPrefix = [NSString stringWithFormat: @"%@:", toUserID];
|
|
keys = [newConfig allKeys];
|
|
max = [keys count];
|
|
for (count = 0; count < max; count++)
|
|
{
|
|
oldKey = [keys objectAtIndex: count];
|
|
if ([oldKey hasPrefix: oldPrefix])
|
|
{
|
|
modified = YES;
|
|
rest = [oldKey substringFromIndex: [oldPrefix length]];
|
|
[newConfig setObject: [newConfig objectForKey: oldKey]
|
|
forKey: [NSString stringWithFormat: @"%@%@", newPrefix, rest]];
|
|
[newConfig removeObjectForKey: oldKey];
|
|
}
|
|
}
|
|
|
|
if (!modified)
|
|
newConfig = nil;
|
|
|
|
return newConfig;
|
|
}
|
|
|
|
- (BOOL) _updateSettings: (SOGoUserSettings *) settings
|
|
forModule: (NSString *) moduleName
|
|
fromUser: (NSString *) fromUserID
|
|
toUser: (NSString *) toUserID
|
|
{
|
|
static NSString *contactsKeys[] = { @"FolderDisplayNames", nil };
|
|
static NSString *calendarKeys[] = { @"FolderDisplayNames", @"FolderColors",
|
|
@"FolderShowAlarms", @"FolderShowTasks",
|
|
@"FolderSyncTags", @"FolderSynchronize",
|
|
@"FreeBusyExclusions", nil };
|
|
BOOL modified = NO;
|
|
NSMutableDictionary *config;
|
|
NSDictionary *modifiedValues;
|
|
NSArray *modifiedSubscriptions;
|
|
NSString **keys, **key;
|
|
|
|
config = [[settings objectForKey: moduleName] mutableCopy];
|
|
if (config)
|
|
{
|
|
if ([moduleName isEqualToString: @"Contacts"])
|
|
keys = contactsKeys;
|
|
else if ([moduleName isEqualToString: @"Calendar"])
|
|
keys = calendarKeys;
|
|
else
|
|
keys = NULL;
|
|
key = keys;
|
|
|
|
if (key)
|
|
{
|
|
while (*key)
|
|
{
|
|
modifiedValues
|
|
= [self _updatedValuesForConfig: [config objectForKey: *key]
|
|
fromUser: fromUserID toUser: toUserID];
|
|
if (modifiedValues)
|
|
{
|
|
[config setObject: modifiedValues forKey: *key];
|
|
modified = YES;
|
|
}
|
|
key++;
|
|
}
|
|
}
|
|
|
|
modifiedSubscriptions
|
|
= [self
|
|
_updateSubscriptionsForConfig: [config objectForKey: @"SubscribedFolders"]
|
|
fromUser: fromUserID
|
|
toUser: toUserID];
|
|
if (modifiedSubscriptions)
|
|
{
|
|
modified = YES;
|
|
[config setObject: modifiedSubscriptions forKey: @"SubscribedFolders"];
|
|
}
|
|
|
|
if (modified)
|
|
[settings setObject: config forKey: moduleName];
|
|
}
|
|
|
|
return modified;
|
|
}
|
|
|
|
- (void) _updateForeignSubscriptionsFromUser: (NSString *) fromUserID
|
|
toUser: (NSString *) toUserID
|
|
forSubscriber: (NSString *) subscriber
|
|
{
|
|
SOGoUserSettings *settings;
|
|
BOOL modified;
|
|
|
|
settings = [SOGoUserSettings settingsForUser: subscriber];
|
|
modified = ([self _updateSettings: settings forModule: @"Calendar"
|
|
fromUser: fromUserID toUser: toUserID]
|
|
|| [self _updateSettings: settings forModule: @"Contacts"
|
|
fromUser: fromUserID toUser: toUserID]);
|
|
if (modified)
|
|
[settings synchronize];
|
|
}
|
|
|
|
- (void) _updateForeignSubscriptionsFromUser: (NSString *) fromUserID
|
|
toUser: (NSString *) toUserID
|
|
{
|
|
NSArray *subscribers;
|
|
NSString *subscriber;
|
|
NSUInteger count, max;
|
|
|
|
subscribers = [self _fetchSubcribersForUser: fromUserID];
|
|
max = [subscribers count];
|
|
for (count = 0; count < max; count++)
|
|
{
|
|
subscriber = [subscribers objectAtIndex: count];
|
|
[self _updateForeignSubscriptionsFromUser: fromUserID
|
|
toUser: toUserID
|
|
forSubscriber: subscriber];
|
|
}
|
|
}
|
|
|
|
- (void) _updateLocalACLsForPath: (NSString *) path
|
|
fromSQLUser: (NSString *) sqlFromUserID
|
|
toSQLUser: (NSString *) sqlToUserID
|
|
{
|
|
GCSFolderManager *fm;
|
|
GCSFolder *folder;
|
|
GCSChannelManager *cm;
|
|
EOAdaptorChannel *ac;
|
|
NSAutoreleasePool *pool;
|
|
NSArray *acls;
|
|
NSString *sql, *qs, *oldObjectPath, *newObjectPath, *rest;
|
|
NSURL *location;
|
|
EOQualifier *qualifier;
|
|
|
|
pool = [NSAutoreleasePool new];
|
|
|
|
fm = [GCSFolderManager defaultFolderManager];
|
|
cm = [fm channelManager];
|
|
folder = [fm folderAtPath: path];
|
|
qs = [NSString stringWithFormat: @"c_object LIKE '/%@/%%'", sqlFromUserID];
|
|
qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
|
|
|
|
acls = [folder fetchAclMatchingQualifier: qualifier];
|
|
if ([acls count] > 0)
|
|
{
|
|
oldObjectPath = [[acls objectAtIndex: 0] objectForKey: @"c_object"];
|
|
rest = [oldObjectPath substringFromIndex: [sqlFromUserID length] + 1];
|
|
newObjectPath = [NSString stringWithFormat: @"/%@%@",
|
|
sqlToUserID, rest];
|
|
location = [folder aclLocation];
|
|
ac = [cm acquireOpenChannelForURL: location];
|
|
sql = [NSString stringWithFormat: @"UPDATE %@ SET c_object = '%@'",
|
|
[location gcsTableName], newObjectPath];
|
|
[ac evaluateExpressionX: sql];
|
|
[cm releaseChannel: ac];
|
|
}
|
|
|
|
[pool release];
|
|
}
|
|
|
|
- (void) _updateLocalACLsFromUser: (NSString *) fromUserID
|
|
toUser: (NSString *) toUserID
|
|
{
|
|
GCSFolderManager *fm;
|
|
GCSChannelManager *cm;
|
|
EOAdaptorChannel *fc;
|
|
NSArray *attrs;
|
|
NSDictionary *row;
|
|
NSString *sql, *sqlFromUserID, *sqlToUserID;
|
|
NSURL *folderLocation;
|
|
|
|
fm = [GCSFolderManager defaultFolderManager];
|
|
cm = [fm channelManager];
|
|
folderLocation = [fm folderInfoLocation];
|
|
fc = [cm acquireOpenChannelForURL: folderLocation];
|
|
sqlFromUserID = [fromUserID asSafeSQLString];
|
|
sqlToUserID = [toUserID asSafeSQLString];
|
|
|
|
sql = [NSString stringWithFormat: @"SELECT c_path FROM %@"
|
|
@" WHERE c_path2 = '%@'",
|
|
[folderLocation gcsTableName], sqlToUserID];
|
|
[fc evaluateExpressionX: sql];
|
|
attrs = [fc describeResults: NO];
|
|
while ((row = [fc fetchAttributes: attrs withZone: NULL]))
|
|
[self _updateLocalACLsForPath: [row objectForKey: @"c_path"]
|
|
fromSQLUser: sqlFromUserID
|
|
toSQLUser: sqlToUserID];
|
|
[fc cancelFetch];
|
|
[cm releaseChannel: fc];
|
|
}
|
|
|
|
- (void) _updateForeignACLsForLocation: (NSString *) locationString
|
|
fromSQLUser: (NSString *) sqlFromUserID
|
|
toSQLUser: (NSString *) sqlToUserID
|
|
{
|
|
NSAutoreleasePool *pool;
|
|
NSURL *location;
|
|
GCSChannelManager *cm;
|
|
EOAdaptorChannel *tc;
|
|
NSString *sql;
|
|
|
|
pool = [NSAutoreleasePool new];
|
|
|
|
cm = [[GCSFolderManager defaultFolderManager] channelManager];
|
|
|
|
location = [NSURL URLWithString: locationString];
|
|
|
|
tc = [cm acquireOpenChannelForURL: location];
|
|
sql = [NSString stringWithFormat: @"UPDATE %@ SET c_uid = '%@'"
|
|
@" WHERE c_uid = '%@'",
|
|
[location gcsTableName],
|
|
sqlToUserID, sqlFromUserID];
|
|
[tc evaluateExpressionX: sql];
|
|
[cm releaseChannel: tc];
|
|
[pool release];
|
|
}
|
|
|
|
- (void) _updateForeignACLsFromUser: (NSString *) fromUserID
|
|
toUser: (NSString *) toUserID
|
|
{
|
|
GCSFolderManager *fm;
|
|
GCSChannelManager *cm;
|
|
EOAdaptorChannel *fc;
|
|
NSArray *attrs;
|
|
NSDictionary *row;
|
|
NSString *sql, *sqlFromUserID, *sqlToUserID;
|
|
NSURL *folderLocation;
|
|
|
|
fm = [GCSFolderManager defaultFolderManager];
|
|
cm = [fm channelManager];
|
|
folderLocation = [fm folderInfoLocation];
|
|
fc = [cm acquireOpenChannelForURL: folderLocation];
|
|
sqlFromUserID = [fromUserID asSafeSQLString];
|
|
sqlToUserID = [toUserID asSafeSQLString];
|
|
|
|
sql = [NSString stringWithFormat: @"SELECT c_acl_location FROM %@"
|
|
@" WHERE c_path2 != '%@'",
|
|
[folderLocation gcsTableName], sqlToUserID];
|
|
[fc evaluateExpressionX: sql];
|
|
attrs = [fc describeResults: NO];
|
|
while ((row = [fc fetchAttributes: attrs withZone: NULL]))
|
|
[self _updateForeignACLsForLocation: [row objectForKey: @"c_acl_location"]
|
|
fromSQLUser: sqlFromUserID
|
|
toSQLUser: sqlToUserID];
|
|
[fc cancelFetch];
|
|
[cm releaseChannel: fc];
|
|
}
|
|
|
|
- (BOOL) proceed
|
|
{
|
|
BOOL rc = NO;
|
|
|
|
if ([self _updateSOGoFolderInfo])
|
|
{
|
|
if ([self _updateSOGoUserProfile])
|
|
{
|
|
[self _updateForeignSubscriptionsFromUser: oldUserID toUser: newUserID];
|
|
[self _updateLocalACLsFromUser: oldUserID toUser: newUserID];
|
|
[self _updateForeignACLsFromUser: oldUserID toUser: newUserID];
|
|
rc = YES;
|
|
}
|
|
|
|
if (!rc)
|
|
[self _rollbackSOGoFolderInfo];
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
- (BOOL) run
|
|
{
|
|
return ([self parseArguments] && [self proceed]);
|
|
}
|
|
|
|
@end
|