From 2607436632c77b4bfd46371810b35ea1459ba29a Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Thu, 16 Jun 2011 14:40:59 +0000 Subject: [PATCH] Monotone-Parent: 7ed8d0e95d642fed8f24b92fc18f0e2abc1b90d0 Monotone-Revision: 869378c97b2bf3386ab9d61f8e107135f0dc0529 Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2011-06-16T14:40:59 Monotone-Branch: ca.inverse.sogo --- ChangeLog | 3 + NEWS | 2 + Tools/GNUmakefile | 1 + Tools/SOGoToolRenameUser.m | 568 +++++++++++++++++++++++++++++++++++++ 4 files changed, 574 insertions(+) create mode 100644 Tools/SOGoToolRenameUser.m diff --git a/ChangeLog b/ChangeLog index fe2328c2a..4ac74c0c7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2011-06-16 Wolfgang Sourdeau + * Tools/SOGoToolRenameUser.m: new sogo-tool module that updates + the references after a change of user id for a specific user. + * SoObjects/SOGo/NSString+Utilities.m (-asSafeSQLString): new method that properly escape strings passed as values in SQL queries. diff --git a/NEWS b/NEWS index b3e63a438..473570415 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,8 @@ --------------------- New Features - initial support for threaded-view in the webmail interface +- sogo-tool: new "rename-user" command that automatically updates all the + references in the database after modifying a user id Enhancements - improved list selection and contextual menu behavior in all web modules diff --git a/Tools/GNUmakefile b/Tools/GNUmakefile index 8ac1e8c29..7fbad4c9d 100644 --- a/Tools/GNUmakefile +++ b/Tools/GNUmakefile @@ -15,6 +15,7 @@ $(SOGO_TOOL)_OBJC_FILES += \ SOGoToolCheckDoubles.m \ SOGoToolRemoveDoubles.m \ SOGoToolRemove.m \ + SOGoToolRenameUser.m \ SOGO_SLAPD_SOCKD = sogo-slapd-sockd $(SOGO_SLAPD_SOCKD)_INSTALL_DIR = $(SOGO_ADMIN_TOOLS) diff --git a/Tools/SOGoToolRenameUser.m b/Tools/SOGoToolRenameUser.m new file mode 100644 index 000000000..860f98e0f --- /dev/null +++ b/Tools/SOGoToolRenameUser.m @@ -0,0 +1,568 @@ +/* SOGoToolRenameUser.m - this file is part of SOGo + * + * Copyright (C) 2011 Inverse inc + * + * Author: Wolfgang Sourdeau + * + * 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 + +#import +#import +#import +#import +#import + +#import +#import +#import + +#import +#import +#import +#import + +#import +#import +#import + +#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