2009-09-25 16:42:33 +02:00
|
|
|
/* SQLSource.h - this file is part of SOGo
|
|
|
|
*
|
2012-02-11 08:08:02 +01:00
|
|
|
* Copyright (C) 2009-2012 Inverse inc.
|
2009-09-25 16:42:33 +02:00
|
|
|
*
|
2011-04-14 20:41:10 +02:00
|
|
|
* Authors: Ludovic Marcotte <lmarcotte@inverse.ca>
|
|
|
|
* Francis Lachapelle <flachapelle@inverse.ca>
|
2009-09-25 16:42:33 +02:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#import <Foundation/NSArray.h>
|
|
|
|
#import <Foundation/NSDictionary.h>
|
2011-12-30 21:39:07 +01:00
|
|
|
#import <Foundation/NSException.h>
|
|
|
|
#import <Foundation/NSObject.h>
|
2009-09-25 16:42:33 +02:00
|
|
|
#import <Foundation/NSString.h>
|
|
|
|
#import <Foundation/NSValue.h>
|
|
|
|
#import <Foundation/NSURL.h>
|
|
|
|
|
2011-06-24 21:19:50 +02:00
|
|
|
#import <NGExtensions/NSNull+misc.h>
|
2009-09-25 16:42:33 +02:00
|
|
|
#import <NGExtensions/NSObject+Logs.h>
|
|
|
|
|
|
|
|
#import <GDLContentStore/GCSChannelManager.h>
|
|
|
|
#import <GDLContentStore/NSURL+GCS.h>
|
2011-04-14 20:41:10 +02:00
|
|
|
#import <GDLContentStore/EOQualifier+GCS.h>
|
2009-09-25 16:42:33 +02:00
|
|
|
#import <GDLAccess/EOAdaptorChannel.h>
|
|
|
|
|
2010-08-12 20:29:13 +02:00
|
|
|
#import "SOGoConstants.h"
|
2010-12-29 14:01:16 +01:00
|
|
|
#import "NSString+Utilities.h"
|
2009-09-25 16:42:33 +02:00
|
|
|
|
2010-08-12 20:29:13 +02:00
|
|
|
#import "SQLSource.h"
|
2010-03-08 16:18:05 +01:00
|
|
|
|
2009-09-25 16:42:33 +02:00
|
|
|
/**
|
|
|
|
* The view MUST contain the following columns:
|
|
|
|
*
|
|
|
|
* c_uid - will be used for authentication - it's a username or username@domain.tld)
|
|
|
|
* c_name - which can be identical to c_uid - will be used to uniquely identify entries)
|
2010-12-09 19:56:03 +01:00
|
|
|
* c_password - password of the user, plain-text, md5 or sha encoded for now
|
2009-09-25 16:42:33 +02:00
|
|
|
* c_cn - the user's common name
|
2010-09-24 17:24:24 +02:00
|
|
|
* mail - the user's mail address
|
2009-09-25 16:42:33 +02:00
|
|
|
*
|
|
|
|
* Other columns can be defined - see LDAPSource.m for the complete list.
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* A SQL source can be defined like this:
|
|
|
|
*
|
|
|
|
* {
|
|
|
|
* id = zot;
|
|
|
|
* type = sql;
|
2012-02-11 08:08:02 +01:00
|
|
|
* viewURL = "mysql://sogo:sogo@127.0.0.1:5432/sogo/sogo_view";
|
2009-09-25 16:42:33 +02:00
|
|
|
* canAuthenticate = YES;
|
|
|
|
* isAddressBook = YES;
|
|
|
|
* userPasswordAlgorithm = md5;
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
@implementation SQLSource
|
|
|
|
|
|
|
|
+ (id) sourceFromUDSource: (NSDictionary *) udSource
|
2009-11-29 05:19:32 +01:00
|
|
|
inDomain: (NSString *) domain
|
2009-09-25 16:42:33 +02:00
|
|
|
{
|
2009-11-29 05:19:32 +01:00
|
|
|
return [[[self alloc] initFromUDSource: udSource
|
|
|
|
inDomain: domain] autorelease];
|
2009-09-25 16:42:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (id) init
|
|
|
|
{
|
|
|
|
if ((self = [super init]))
|
|
|
|
{
|
|
|
|
_sourceID = nil;
|
2012-02-11 08:08:02 +01:00
|
|
|
_domainField = nil;
|
2011-04-14 20:41:10 +02:00
|
|
|
_authenticationFilter = nil;
|
2011-10-26 17:25:49 +02:00
|
|
|
_loginFields = nil;
|
2009-09-25 16:42:33 +02:00
|
|
|
_mailFields = nil;
|
|
|
|
_userPasswordAlgorithm = nil;
|
|
|
|
_viewURL = nil;
|
2011-04-28 15:36:29 +02:00
|
|
|
_kindField = nil;
|
|
|
|
_multipleBookingsField = nil;
|
2012-02-22 19:37:58 +01:00
|
|
|
_imapHostField = nil;
|
2009-09-25 16:42:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) dealloc
|
|
|
|
{
|
|
|
|
[_sourceID release];
|
2011-04-14 20:41:10 +02:00
|
|
|
[_authenticationFilter release];
|
2011-10-26 17:25:49 +02:00
|
|
|
[_loginFields release];
|
2009-09-25 16:42:33 +02:00
|
|
|
[_mailFields release];
|
|
|
|
[_userPasswordAlgorithm release];
|
|
|
|
[_viewURL release];
|
2011-04-28 15:36:29 +02:00
|
|
|
[_kindField release];
|
|
|
|
[_multipleBookingsField release];
|
2012-02-11 08:08:02 +01:00
|
|
|
[_domainField release];
|
2012-02-22 19:37:58 +01:00
|
|
|
[_imapHostField release];
|
2009-09-25 16:42:33 +02:00
|
|
|
|
|
|
|
[super dealloc];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (id) initFromUDSource: (NSDictionary *) udSource
|
2009-11-29 05:19:32 +01:00
|
|
|
inDomain: (NSString *) sourceDomain
|
2009-09-25 16:42:33 +02:00
|
|
|
{
|
|
|
|
self = [self init];
|
|
|
|
|
|
|
|
ASSIGN(_sourceID, [udSource objectForKey: @"id"]);
|
2011-04-14 20:41:10 +02:00
|
|
|
ASSIGN(_authenticationFilter, [udSource objectForKey: @"authenticationFilter"]);
|
2011-10-26 17:25:49 +02:00
|
|
|
ASSIGN(_loginFields, [udSource objectForKey: @"LoginFieldNames"]);
|
2009-09-25 16:42:33 +02:00
|
|
|
ASSIGN(_mailFields, [udSource objectForKey: @"MailFieldNames"]);
|
|
|
|
ASSIGN(_userPasswordAlgorithm, [udSource objectForKey: @"userPasswordAlgorithm"]);
|
2011-04-14 20:41:10 +02:00
|
|
|
ASSIGN(_imapLoginField, [udSource objectForKey: @"IMAPLoginFieldName"]);
|
2012-02-22 19:37:58 +01:00
|
|
|
ASSIGN(_imapHostField, [udSource objectForKey: @"IMAPHostFieldName"]);
|
2011-04-28 15:36:29 +02:00
|
|
|
ASSIGN(_kindField, [udSource objectForKey: @"KindFieldName"]);
|
|
|
|
ASSIGN(_multipleBookingsField, [udSource objectForKey: @"MultipleBookingsFieldName"]);
|
2012-02-11 08:08:02 +01:00
|
|
|
ASSIGN(_domainField, [udSource objectForKey: @"DomainFieldName"]);
|
2011-04-28 15:36:29 +02:00
|
|
|
|
2009-09-25 16:42:33 +02:00
|
|
|
if (!_userPasswordAlgorithm)
|
|
|
|
_userPasswordAlgorithm = @"none";
|
|
|
|
|
|
|
|
if ([udSource objectForKey: @"viewURL"])
|
|
|
|
_viewURL = [[NSURL alloc] initWithString: [udSource objectForKey: @"viewURL"]];
|
2009-11-29 05:19:32 +01:00
|
|
|
|
|
|
|
#warning this domain code has no effect yet
|
|
|
|
if ([sourceDomain length])
|
2009-11-29 22:44:04 +01:00
|
|
|
ASSIGN (_domain, sourceDomain);
|
2009-11-29 05:19:32 +01:00
|
|
|
|
2009-09-25 16:42:33 +02:00
|
|
|
if (!_viewURL)
|
|
|
|
{
|
|
|
|
[self autorelease];
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2009-11-29 05:19:32 +01:00
|
|
|
- (NSString *) domain
|
|
|
|
{
|
|
|
|
return _domain;
|
|
|
|
}
|
|
|
|
|
2009-09-25 16:42:33 +02:00
|
|
|
- (BOOL) _isPassword: (NSString *) plainPassword
|
|
|
|
equalTo: (NSString *) encryptedPassword
|
|
|
|
{
|
2011-03-29 16:34:14 +02:00
|
|
|
if (!plainPassword || !encryptedPassword)
|
|
|
|
return NO;
|
|
|
|
|
2009-09-25 16:42:33 +02:00
|
|
|
if ([_userPasswordAlgorithm caseInsensitiveCompare: @"none"] == NSOrderedSame)
|
|
|
|
{
|
|
|
|
return [plainPassword isEqualToString: encryptedPassword];
|
|
|
|
}
|
2010-08-03 16:49:28 +02:00
|
|
|
else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"crypt"] == NSOrderedSame)
|
|
|
|
{
|
2011-02-02 02:58:56 +01:00
|
|
|
return [[plainPassword asCryptStringUsingSalt: encryptedPassword] isEqualToString: encryptedPassword];
|
2010-08-03 16:49:28 +02:00
|
|
|
}
|
2009-09-25 16:42:33 +02:00
|
|
|
else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"md5"] == NSOrderedSame)
|
|
|
|
{
|
2010-12-29 14:01:16 +01:00
|
|
|
return [[plainPassword asMD5String] isEqualToString: encryptedPassword];
|
2009-09-25 16:42:33 +02:00
|
|
|
}
|
2010-12-09 19:56:03 +01:00
|
|
|
else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"sha"] == NSOrderedSame)
|
|
|
|
{
|
|
|
|
|
2010-12-29 14:01:16 +01:00
|
|
|
return [[plainPassword asSHA1String] isEqualToString: encryptedPassword];
|
2010-12-09 19:56:03 +01:00
|
|
|
}
|
2009-09-25 16:42:33 +02:00
|
|
|
|
|
|
|
|
|
|
|
[self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm];
|
|
|
|
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
2011-03-24 15:11:48 +01:00
|
|
|
/**
|
|
|
|
* Encrypts a string using this source password algorithm.
|
|
|
|
* @param plainPassword the unencrypted password.
|
|
|
|
* @return a new encrypted string.
|
|
|
|
* @see _isPassword:equalTo:
|
|
|
|
*/
|
|
|
|
- (NSString *) _encryptPassword: (NSString *) plainPassword
|
|
|
|
{
|
|
|
|
if ([_userPasswordAlgorithm caseInsensitiveCompare: @"none"] == NSOrderedSame)
|
|
|
|
{
|
|
|
|
return plainPassword;
|
|
|
|
}
|
|
|
|
else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"crypt"] == NSOrderedSame)
|
|
|
|
{
|
|
|
|
return [plainPassword asCryptStringUsingSalt: [plainPassword asMD5String]];
|
|
|
|
}
|
|
|
|
else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"md5"] == NSOrderedSame)
|
|
|
|
{
|
|
|
|
return [plainPassword asMD5String];
|
|
|
|
}
|
|
|
|
else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"sha"] == NSOrderedSame)
|
|
|
|
{
|
|
|
|
return [plainPassword asSHA1String];
|
|
|
|
}
|
|
|
|
|
|
|
|
[self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm];
|
|
|
|
|
|
|
|
return plainPassword;
|
|
|
|
}
|
|
|
|
|
2010-03-08 16:18:05 +01:00
|
|
|
//
|
|
|
|
// SQL sources don't support right now all the password policy
|
|
|
|
// stuff supported by OpenLDAP (and others). If we want to support
|
|
|
|
// this for SQL sources, we'll have to implement the same
|
|
|
|
// kind of logic in this module.
|
|
|
|
//
|
|
|
|
- (BOOL) checkLogin: (NSString *) _login
|
|
|
|
password: (NSString *) _pwd
|
|
|
|
perr: (SOGoPasswordPolicyError *) _perr
|
|
|
|
expire: (int *) _expire
|
|
|
|
grace: (int *) _grace
|
2009-09-25 16:42:33 +02:00
|
|
|
{
|
|
|
|
EOAdaptorChannel *channel;
|
2011-04-14 20:41:10 +02:00
|
|
|
EOQualifier *qualifier;
|
2009-09-25 16:42:33 +02:00
|
|
|
GCSChannelManager *cm;
|
|
|
|
NSException *ex;
|
2011-04-14 20:41:10 +02:00
|
|
|
NSMutableString *sql;
|
2009-11-19 21:02:45 +01:00
|
|
|
BOOL rc;
|
|
|
|
|
|
|
|
rc = NO;
|
2009-09-25 16:42:33 +02:00
|
|
|
|
2010-03-25 02:20:06 +01:00
|
|
|
_login = [_login stringByReplacingString: @"'" withString: @"''"];
|
2009-09-25 16:42:33 +02:00
|
|
|
cm = [GCSChannelManager defaultChannelManager];
|
|
|
|
channel = [cm acquireOpenChannelForURL: _viewURL];
|
2009-11-19 21:02:45 +01:00
|
|
|
if (channel)
|
2011-04-14 20:41:10 +02:00
|
|
|
{
|
2011-10-26 17:25:49 +02:00
|
|
|
if (_loginFields)
|
|
|
|
{
|
|
|
|
NSMutableArray *qualifiers;
|
|
|
|
NSString *field;
|
|
|
|
EOQualifier *loginQualifier;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
qualifiers = [NSMutableArray arrayWithCapacity: [_loginFields count]];
|
|
|
|
for (i = 0; i < [_loginFields count]; i++)
|
|
|
|
{
|
|
|
|
field = [_loginFields objectAtIndex: i];
|
|
|
|
loginQualifier = [[EOKeyValueQualifier alloc] initWithKey: field
|
|
|
|
operatorSelector: EOQualifierOperatorEqual
|
|
|
|
value: _login];
|
|
|
|
[loginQualifier autorelease];
|
|
|
|
[qualifiers addObject: loginQualifier];
|
|
|
|
}
|
|
|
|
qualifier = [[EOOrQualifier alloc] initWithQualifierArray: qualifiers];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"c_uid"
|
|
|
|
operatorSelector: EOQualifierOperatorEqual
|
|
|
|
value: _login];
|
|
|
|
}
|
2011-04-14 20:41:10 +02:00
|
|
|
[qualifier autorelease];
|
|
|
|
sql = [NSMutableString stringWithFormat: @"SELECT c_password"
|
|
|
|
@" FROM %@"
|
|
|
|
@" WHERE ",
|
|
|
|
[_viewURL gcsTableName]];
|
|
|
|
if (_authenticationFilter)
|
|
|
|
{
|
|
|
|
qualifier = [[EOAndQualifier alloc] initWithQualifiers:
|
|
|
|
qualifier,
|
|
|
|
[EOQualifier qualifierWithQualifierFormat: _authenticationFilter],
|
|
|
|
nil];
|
|
|
|
[qualifier autorelease];
|
|
|
|
}
|
|
|
|
[qualifier _gcsAppendToString: sql];
|
|
|
|
|
2009-11-19 21:02:45 +01:00
|
|
|
ex = [channel evaluateExpressionX: sql];
|
|
|
|
if (!ex)
|
|
|
|
{
|
|
|
|
NSDictionary *row;
|
|
|
|
NSArray *attrs;
|
|
|
|
NSString *value;
|
|
|
|
|
|
|
|
attrs = [channel describeResults: NO];
|
|
|
|
row = [channel fetchAttributes: attrs withZone: NULL];
|
|
|
|
value = [row objectForKey: @"c_password"];
|
2009-09-25 16:42:33 +02:00
|
|
|
|
2010-03-08 16:18:05 +01:00
|
|
|
rc = [self _isPassword: _pwd equalTo: value];
|
2009-11-24 21:19:21 +01:00
|
|
|
[channel cancelFetch];
|
2009-11-19 21:02:45 +01:00
|
|
|
}
|
|
|
|
else
|
2011-04-14 20:41:10 +02:00
|
|
|
[self errorWithFormat: @"could not run SQL '%@': %@", qualifier, ex];
|
|
|
|
|
2009-11-19 21:02:45 +01:00
|
|
|
[cm releaseChannel: channel];
|
2009-09-25 16:42:33 +02:00
|
|
|
}
|
|
|
|
else
|
2009-11-19 21:02:45 +01:00
|
|
|
[self errorWithFormat:@"failed to acquire channel for URL: %@",
|
|
|
|
[_viewURL absoluteString]];
|
2009-09-25 16:42:33 +02:00
|
|
|
|
2009-11-19 21:02:45 +01:00
|
|
|
return rc;
|
2009-09-25 16:42:33 +02:00
|
|
|
}
|
|
|
|
|
2011-03-24 15:11:48 +01:00
|
|
|
/**
|
|
|
|
* Change a user's password.
|
|
|
|
* @param login the user's login name.
|
|
|
|
* @param oldPassword the previous password.
|
|
|
|
* @param newPassword the new password.
|
|
|
|
* @param perr is not used.
|
|
|
|
* @return YES if the password was successfully changed.
|
|
|
|
*/
|
2010-03-08 16:18:05 +01:00
|
|
|
- (BOOL) changePasswordForLogin: (NSString *) login
|
|
|
|
oldPassword: (NSString *) oldPassword
|
|
|
|
newPassword: (NSString *) newPassword
|
|
|
|
perr: (SOGoPasswordPolicyError *) perr
|
|
|
|
{
|
2011-03-24 15:11:48 +01:00
|
|
|
EOAdaptorChannel *channel;
|
|
|
|
GCSChannelManager *cm;
|
|
|
|
NSException *ex;
|
|
|
|
NSString *sqlstr;
|
|
|
|
BOOL didChange;
|
|
|
|
BOOL isOldPwdOk;
|
|
|
|
|
|
|
|
isOldPwdOk = NO;
|
|
|
|
didChange = NO;
|
|
|
|
|
|
|
|
// Verify current password
|
|
|
|
isOldPwdOk = [self checkLogin:login password:oldPassword perr:perr expire:0 grace:0];
|
|
|
|
|
|
|
|
if (isOldPwdOk)
|
|
|
|
{
|
|
|
|
// Encrypt new password
|
|
|
|
NSString *encryptedPassword = [self _encryptPassword: newPassword];
|
|
|
|
|
|
|
|
// Save new password
|
|
|
|
login = [login stringByReplacingString: @"'" withString: @"''"];
|
|
|
|
cm = [GCSChannelManager defaultChannelManager];
|
|
|
|
channel = [cm acquireOpenChannelForURL: _viewURL];
|
|
|
|
if (channel)
|
|
|
|
{
|
|
|
|
sqlstr = [NSString stringWithFormat: (@"UPDATE %@"
|
|
|
|
@" SET c_password = '%@'"
|
|
|
|
@" WHERE c_uid = '%@'"),
|
|
|
|
[_viewURL gcsTableName], encryptedPassword, login];
|
|
|
|
|
|
|
|
ex = [channel evaluateExpressionX: sqlstr];
|
|
|
|
if (!ex)
|
|
|
|
{
|
|
|
|
didChange = YES;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[self errorWithFormat: @"could not run SQL '%@': %@", sqlstr, ex];
|
|
|
|
}
|
|
|
|
[cm releaseChannel: channel];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return didChange;
|
2010-03-08 16:18:05 +01:00
|
|
|
}
|
|
|
|
|
2011-01-05 21:59:47 +01:00
|
|
|
- (NSString *) _whereClauseFromArray: (NSArray *) theArray
|
|
|
|
value: (NSString *) theValue
|
|
|
|
exact: (BOOL) theBOOL
|
|
|
|
{
|
|
|
|
NSMutableString *s;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
s = [NSMutableString string];
|
|
|
|
|
|
|
|
for (i = 0; i < [theArray count]; i++)
|
|
|
|
{
|
|
|
|
if (theBOOL)
|
|
|
|
[s appendFormat: @" OR LOWER(%@) = '%@'", [theArray objectAtIndex: i], theValue];
|
|
|
|
else
|
|
|
|
[s appendFormat: @" OR LOWER(%@) LIKE '%%%@%%'", [theArray objectAtIndex: i], theValue];
|
|
|
|
}
|
|
|
|
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
2009-09-25 16:42:33 +02:00
|
|
|
- (NSDictionary *) _lookupContactEntry: (NSString *) theID
|
|
|
|
considerEmail: (BOOL) b
|
2012-02-11 08:08:02 +01:00
|
|
|
inDomain: (NSString *) domain
|
2009-09-25 16:42:33 +02:00
|
|
|
{
|
2011-03-04 21:30:28 +01:00
|
|
|
NSMutableDictionary *response;
|
2011-10-26 17:25:49 +02:00
|
|
|
NSMutableArray *qualifiers;
|
2011-12-30 21:39:07 +01:00
|
|
|
NSArray *fieldNames;
|
2009-09-25 16:42:33 +02:00
|
|
|
EOAdaptorChannel *channel;
|
2012-02-11 08:08:02 +01:00
|
|
|
EOQualifier *loginQualifier, *domainQualifier, *qualifier;
|
2009-09-25 16:42:33 +02:00
|
|
|
GCSChannelManager *cm;
|
2011-04-14 20:41:10 +02:00
|
|
|
NSMutableString *sql;
|
2011-10-26 17:25:49 +02:00
|
|
|
NSString *value, *field;
|
2009-09-25 16:42:33 +02:00
|
|
|
NSException *ex;
|
2011-10-26 17:25:49 +02:00
|
|
|
int i;
|
2009-11-19 21:02:45 +01:00
|
|
|
|
|
|
|
response = nil;
|
2009-09-25 16:42:33 +02:00
|
|
|
|
2010-03-25 02:20:06 +01:00
|
|
|
theID = [theID stringByReplacingString: @"'" withString: @"''"];
|
2009-09-25 16:42:33 +02:00
|
|
|
cm = [GCSChannelManager defaultChannelManager];
|
|
|
|
channel = [cm acquireOpenChannelForURL: _viewURL];
|
2009-11-19 21:02:45 +01:00
|
|
|
if (channel)
|
2009-09-25 16:42:33 +02:00
|
|
|
{
|
2011-10-26 17:25:49 +02:00
|
|
|
qualifiers = [NSMutableArray arrayWithCapacity: [_loginFields count] + 1];
|
|
|
|
|
|
|
|
// Always compare against the c_uid field
|
|
|
|
loginQualifier = [[EOKeyValueQualifier alloc] initWithKey: @"c_uid"
|
|
|
|
operatorSelector: EOQualifierOperatorEqual
|
|
|
|
value: theID];
|
|
|
|
[loginQualifier autorelease];
|
|
|
|
[qualifiers addObject: loginQualifier];
|
|
|
|
|
|
|
|
if (_loginFields)
|
|
|
|
{
|
|
|
|
for (i = 0; i < [_loginFields count]; i++)
|
|
|
|
{
|
|
|
|
field = [_loginFields objectAtIndex: i];
|
|
|
|
if ([field caseInsensitiveCompare: @"c_uid"] != NSOrderedSame)
|
|
|
|
{
|
|
|
|
loginQualifier = [[EOKeyValueQualifier alloc] initWithKey: field
|
|
|
|
operatorSelector: EOQualifierOperatorEqual
|
|
|
|
value: theID];
|
|
|
|
[loginQualifier autorelease];
|
|
|
|
[qualifiers addObject: loginQualifier];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-02-11 08:08:02 +01:00
|
|
|
domainQualifier = nil;
|
|
|
|
if (_domainField && domain)
|
|
|
|
{
|
|
|
|
domainQualifier = [[EOKeyValueQualifier alloc] initWithKey: _domainField
|
|
|
|
operatorSelector: EOQualifierOperatorEqual
|
|
|
|
value: domain];
|
|
|
|
[domainQualifier autorelease];
|
|
|
|
}
|
|
|
|
|
2011-10-26 17:25:49 +02:00
|
|
|
if (b)
|
|
|
|
{
|
|
|
|
// Always compare againts the mail field
|
|
|
|
loginQualifier = [[EOKeyValueQualifier alloc] initWithKey: @"mail"
|
|
|
|
operatorSelector: EOQualifierOperatorEqual
|
|
|
|
value: [theID lowercaseString]];
|
|
|
|
[loginQualifier autorelease];
|
|
|
|
[qualifiers addObject: loginQualifier];
|
2011-04-14 20:41:10 +02:00
|
|
|
|
2011-10-26 17:25:49 +02:00
|
|
|
if (_mailFields)
|
2011-01-05 21:59:47 +01:00
|
|
|
{
|
2011-10-26 17:25:49 +02:00
|
|
|
for (i = 0; i < [_mailFields count]; i++)
|
|
|
|
{
|
|
|
|
field = [_mailFields objectAtIndex: i];
|
|
|
|
if ([field caseInsensitiveCompare: @"mail"] != NSOrderedSame
|
|
|
|
&& ![_loginFields containsObject: field])
|
|
|
|
{
|
|
|
|
loginQualifier = [[EOKeyValueQualifier alloc] initWithKey: field
|
|
|
|
operatorSelector: EOQualifierOperatorEqual
|
|
|
|
value: [theID lowercaseString]];
|
|
|
|
[loginQualifier autorelease];
|
|
|
|
[qualifiers addObject: loginQualifier];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-01-05 21:59:47 +01:00
|
|
|
}
|
2011-10-26 17:25:49 +02:00
|
|
|
|
|
|
|
sql = [NSMutableString stringWithFormat: @"SELECT *"
|
|
|
|
@" FROM %@"
|
|
|
|
@" WHERE ",
|
|
|
|
[_viewURL gcsTableName]];
|
|
|
|
qualifier = [[EOOrQualifier alloc] initWithQualifierArray: qualifiers];
|
2012-02-11 08:08:02 +01:00
|
|
|
if (domainQualifier)
|
|
|
|
qualifier = [[EOAndQualifier alloc] initWithQualifiers: domainQualifier, qualifier, nil];
|
2011-10-26 17:25:49 +02:00
|
|
|
[qualifier _gcsAppendToString: sql];
|
|
|
|
|
2009-11-19 21:02:45 +01:00
|
|
|
ex = [channel evaluateExpressionX: sql];
|
|
|
|
if (!ex)
|
|
|
|
{
|
2011-01-05 21:59:47 +01:00
|
|
|
NSMutableArray *emails;
|
|
|
|
|
2009-11-19 21:02:45 +01:00
|
|
|
response = [[channel fetchAttributes: [channel describeResults: NO]
|
|
|
|
withZone: NULL] mutableCopy];
|
|
|
|
[response autorelease];
|
2009-11-24 21:19:21 +01:00
|
|
|
[channel cancelFetch];
|
2009-11-19 21:02:45 +01:00
|
|
|
|
2011-12-30 21:39:07 +01:00
|
|
|
/* Convert all c_ fields to obtain their ldif equivalent */
|
|
|
|
fieldNames = [response allKeys];
|
|
|
|
for (i = 0; i < [fieldNames count]; i++)
|
|
|
|
{
|
|
|
|
field = [fieldNames objectAtIndex: i];
|
|
|
|
if ([field hasPrefix: @"c_"])
|
|
|
|
[response setObject: [response objectForKey: field]
|
|
|
|
forKey: [field substringFromIndex: 2]];
|
|
|
|
}
|
|
|
|
|
2009-11-19 21:02:45 +01:00
|
|
|
// We have to do this here since we do not manage modules
|
|
|
|
// constraints right now over a SQL backend.
|
|
|
|
[response setObject: [NSNumber numberWithBool: YES] forKey: @"CalendarAccess"];
|
|
|
|
[response setObject: [NSNumber numberWithBool: YES] forKey: @"MailAccess"];
|
2011-01-05 21:59:47 +01:00
|
|
|
|
2011-03-04 21:30:28 +01:00
|
|
|
// We set the domain, if any
|
2012-02-11 08:08:02 +01:00
|
|
|
value = nil;
|
2011-03-04 21:30:28 +01:00
|
|
|
if (_domain)
|
|
|
|
value = _domain;
|
2012-02-11 08:08:02 +01:00
|
|
|
else if (_domainField)
|
|
|
|
value = [response objectForKey: _domainField];
|
|
|
|
if (![value isNotNull])
|
2011-03-04 21:30:28 +01:00
|
|
|
value = @"";
|
|
|
|
[response setObject: value forKey: @"c_domain"];
|
|
|
|
|
2011-01-05 21:59:47 +01:00
|
|
|
// We populate all mail fields
|
|
|
|
emails = [NSMutableArray array];
|
|
|
|
|
|
|
|
if ([response objectForKey: @"mail"])
|
|
|
|
[emails addObject: [response objectForKey: @"mail"]];
|
|
|
|
|
|
|
|
if (_mailFields && [_mailFields count] > 0)
|
|
|
|
{
|
|
|
|
NSString *s;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < [_mailFields count]; i++)
|
2011-12-30 14:38:17 +01:00
|
|
|
if ((s = [response objectForKey: [_mailFields objectAtIndex: i]]) &&
|
|
|
|
[[s stringByTrimmingSpaces] length] > 0)
|
2011-01-05 21:59:47 +01:00
|
|
|
[emails addObject: s];
|
|
|
|
}
|
|
|
|
|
|
|
|
[response setObject: emails forKey: @"c_emails"];
|
2012-02-22 19:37:58 +01:00
|
|
|
if (_imapHostField)
|
|
|
|
{
|
|
|
|
value = [response objectForKey: _imapHostField];
|
|
|
|
if ([value isNotNull])
|
|
|
|
[response setObject: value forKey: @"c_imaphostname"];
|
|
|
|
}
|
2011-04-14 20:41:10 +02:00
|
|
|
|
|
|
|
// We check if the user can authenticate
|
|
|
|
if (_authenticationFilter)
|
|
|
|
{
|
|
|
|
EOQualifier *q_uid, *q_auth;
|
|
|
|
|
|
|
|
sql = [NSMutableString stringWithFormat: @"SELECT c_uid"
|
|
|
|
@" FROM %@"
|
|
|
|
@" WHERE ",
|
|
|
|
[_viewURL gcsTableName]];
|
|
|
|
|
|
|
|
q_auth = [EOQualifier qualifierWithQualifierFormat: _authenticationFilter];
|
|
|
|
|
|
|
|
q_uid = [[EOKeyValueQualifier alloc] initWithKey: @"c_uid"
|
|
|
|
operatorSelector: EOQualifierOperatorEqual
|
|
|
|
value: theID];
|
|
|
|
[q_uid autorelease];
|
|
|
|
|
|
|
|
qualifier = [[EOAndQualifier alloc] initWithQualifiers: q_uid, q_auth, nil];
|
|
|
|
[qualifier autorelease];
|
|
|
|
[qualifier _gcsAppendToString: sql];
|
|
|
|
|
|
|
|
ex = [channel evaluateExpressionX: sql];
|
|
|
|
if (!ex)
|
|
|
|
{
|
|
|
|
NSDictionary *authResponse;
|
|
|
|
|
|
|
|
authResponse = [channel fetchAttributes: [channel describeResults: NO] withZone: NULL];
|
|
|
|
[response setObject: [NSNumber numberWithBool: [authResponse count] > 0] forKey: @"canAuthenticate"];
|
|
|
|
[channel cancelFetch];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
[self errorWithFormat: @"could not run SQL '%@': %@", sql, ex];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
[response setObject: [NSNumber numberWithBool: YES] forKey: @"canAuthenticate"];
|
|
|
|
|
|
|
|
// We check if we should use a different login for IMAP
|
|
|
|
if (_imapLoginField)
|
|
|
|
{
|
2011-06-24 21:19:50 +02:00
|
|
|
if ([[response objectForKey: _imapLoginField] isNotNull])
|
2011-04-14 20:41:10 +02:00
|
|
|
[response setObject: [response objectForKey: _imapLoginField] forKey: @"c_imaplogin"];
|
|
|
|
}
|
|
|
|
|
2011-04-28 15:36:29 +02:00
|
|
|
// We check if it's a resource of not
|
|
|
|
if (_kindField)
|
|
|
|
{
|
2012-02-22 15:44:22 +01:00
|
|
|
if ((value = [response objectForKey: _kindField]) && [value isNotNull])
|
2011-04-28 15:36:29 +02:00
|
|
|
{
|
|
|
|
if ([value caseInsensitiveCompare: @"location"] == NSOrderedSame ||
|
|
|
|
[value caseInsensitiveCompare: @"thing"] == NSOrderedSame ||
|
|
|
|
[value caseInsensitiveCompare: @"group"] == NSOrderedSame)
|
|
|
|
{
|
|
|
|
[response setObject: [NSNumber numberWithInt: 1]
|
|
|
|
forKey: @"isResource"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_multipleBookingsField)
|
|
|
|
{
|
|
|
|
if ((value = [response objectForKey: _multipleBookingsField]))
|
|
|
|
{
|
|
|
|
[response setObject: [NSNumber numberWithInt: [value intValue]]
|
|
|
|
forKey: @"numberOfSimultaneousBookings"];
|
|
|
|
}
|
|
|
|
}
|
2012-01-16 21:18:39 +01:00
|
|
|
|
|
|
|
[response setObject: self forKey: @"source"];
|
2009-11-19 21:02:45 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
[self errorWithFormat: @"could not run SQL '%@': %@", sql, ex];
|
|
|
|
[cm releaseChannel: channel];
|
2009-09-25 16:42:33 +02:00
|
|
|
}
|
|
|
|
else
|
2009-11-19 21:02:45 +01:00
|
|
|
[self errorWithFormat:@"failed to acquire channel for URL: %@",
|
|
|
|
[_viewURL absoluteString]];
|
2009-09-25 16:42:33 +02:00
|
|
|
|
2009-11-19 21:02:45 +01:00
|
|
|
return response;
|
2009-09-25 16:42:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (NSDictionary *) lookupContactEntry: (NSString *) theID
|
|
|
|
{
|
2012-02-11 08:08:02 +01:00
|
|
|
return [self _lookupContactEntry: theID considerEmail: NO inDomain: nil];
|
2009-09-25 16:42:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (NSDictionary *) lookupContactEntryWithUIDorEmail: (NSString *) entryID
|
2012-02-11 08:08:02 +01:00
|
|
|
inDomain: (NSString *) domain
|
2009-09-25 16:42:33 +02:00
|
|
|
{
|
2012-02-11 08:08:02 +01:00
|
|
|
return [self _lookupContactEntry: entryID considerEmail: YES inDomain: domain];
|
2009-09-25 16:42:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (NSArray *) allEntryIDs
|
|
|
|
{
|
|
|
|
EOAdaptorChannel *channel;
|
|
|
|
NSMutableArray *results;
|
|
|
|
GCSChannelManager *cm;
|
|
|
|
NSException *ex;
|
|
|
|
NSString *sql;
|
|
|
|
|
|
|
|
results = [NSMutableArray array];
|
|
|
|
|
|
|
|
cm = [GCSChannelManager defaultChannelManager];
|
|
|
|
channel = [cm acquireOpenChannelForURL: _viewURL];
|
2009-11-19 21:02:45 +01:00
|
|
|
if (channel)
|
2009-09-25 16:42:33 +02:00
|
|
|
{
|
2009-11-19 21:02:45 +01:00
|
|
|
sql = [NSString stringWithFormat: @"SELECT c_uid FROM %@",
|
|
|
|
[_viewURL gcsTableName]];
|
|
|
|
|
|
|
|
ex = [channel evaluateExpressionX: sql];
|
|
|
|
if (!ex)
|
|
|
|
{
|
|
|
|
NSDictionary *row;
|
|
|
|
NSArray *attrs;
|
|
|
|
NSString *value;
|
|
|
|
|
|
|
|
attrs = [channel describeResults: NO];
|
|
|
|
|
|
|
|
while ((row = [channel fetchAttributes: attrs withZone: NULL]))
|
|
|
|
{
|
|
|
|
value = [row objectForKey: @"c_uid"];
|
|
|
|
if (value)
|
|
|
|
[results addObject: value];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
[self errorWithFormat: @"could not run SQL '%@': %@", sql, ex];
|
|
|
|
[cm releaseChannel: channel];
|
2009-09-25 16:42:33 +02:00
|
|
|
}
|
|
|
|
else
|
2009-11-19 21:02:45 +01:00
|
|
|
[self errorWithFormat:@"failed to acquire channel for URL: %@",
|
|
|
|
[_viewURL absoluteString]];
|
|
|
|
|
2009-09-25 16:42:33 +02:00
|
|
|
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSArray *) fetchContactsMatching: (NSString *) filter
|
2012-02-11 08:08:02 +01:00
|
|
|
inDomain: (NSString *) domain
|
2009-09-25 16:42:33 +02:00
|
|
|
{
|
|
|
|
EOAdaptorChannel *channel;
|
|
|
|
NSMutableArray *results;
|
|
|
|
GCSChannelManager *cm;
|
|
|
|
NSException *ex;
|
2012-02-11 08:08:02 +01:00
|
|
|
NSMutableString *sql;
|
|
|
|
NSString *lowerFilter;
|
2009-09-25 16:42:33 +02:00
|
|
|
|
|
|
|
results = [NSMutableArray array];
|
|
|
|
|
|
|
|
cm = [GCSChannelManager defaultChannelManager];
|
|
|
|
channel = [cm acquireOpenChannelForURL: _viewURL];
|
2009-11-19 21:02:45 +01:00
|
|
|
if (channel)
|
2009-09-25 16:42:33 +02:00
|
|
|
{
|
2009-11-19 21:02:45 +01:00
|
|
|
lowerFilter = [filter lowercaseString];
|
2010-03-25 02:20:06 +01:00
|
|
|
lowerFilter = [lowerFilter stringByReplacingString: @"'" withString: @"''"];
|
|
|
|
|
2012-02-11 08:08:02 +01:00
|
|
|
sql = [NSMutableString stringWithFormat: (@"SELECT *"
|
2009-11-19 21:02:45 +01:00
|
|
|
@" FROM %@"
|
2012-02-11 08:08:02 +01:00
|
|
|
@" WHERE"
|
|
|
|
@" (LOWER(c_cn) LIKE '%%%@%%'"
|
|
|
|
@" OR LOWER(mail) LIKE '%%%@%%'"),
|
2009-11-19 21:02:45 +01:00
|
|
|
[_viewURL gcsTableName],
|
|
|
|
lowerFilter, lowerFilter];
|
2012-02-11 08:08:02 +01:00
|
|
|
|
2011-01-05 21:59:47 +01:00
|
|
|
if (_mailFields && [_mailFields count] > 0)
|
|
|
|
{
|
2012-02-11 08:08:02 +01:00
|
|
|
[sql appendString: [self _whereClauseFromArray: _mailFields value: lowerFilter exact: NO]];
|
2011-01-05 21:59:47 +01:00
|
|
|
}
|
|
|
|
|
2012-02-11 08:08:02 +01:00
|
|
|
[sql appendString: @")"];
|
|
|
|
|
|
|
|
if (_domainField)
|
|
|
|
{
|
|
|
|
if ([domain length])
|
|
|
|
[sql appendFormat: @" AND %@ = '%@'", _domainField, domain];
|
|
|
|
else
|
|
|
|
[sql appendFormat: @" AND %@ IS NULL", _domainField];
|
|
|
|
}
|
|
|
|
|
2009-11-19 21:02:45 +01:00
|
|
|
ex = [channel evaluateExpressionX: sql];
|
|
|
|
if (!ex)
|
|
|
|
{
|
|
|
|
NSDictionary *row;
|
|
|
|
NSArray *attrs;
|
|
|
|
|
|
|
|
attrs = [channel describeResults: NO];
|
|
|
|
|
|
|
|
while ((row = [channel fetchAttributes: attrs withZone: NULL]))
|
2012-01-16 21:18:39 +01:00
|
|
|
{
|
|
|
|
row = [row mutableCopy];
|
|
|
|
[(NSMutableDictionary *) row setObject: self forKey: @"source"];
|
|
|
|
[results addObject: row];
|
|
|
|
[row release];
|
|
|
|
}
|
2009-11-19 21:02:45 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
[self errorWithFormat: @"could not run SQL '%@': %@", sql, ex];
|
|
|
|
[cm releaseChannel: channel];
|
2009-09-25 16:42:33 +02:00
|
|
|
}
|
|
|
|
else
|
2009-11-19 21:02:45 +01:00
|
|
|
[self errorWithFormat:@"failed to acquire channel for URL: %@",
|
|
|
|
[_viewURL absoluteString]];
|
2009-09-25 16:42:33 +02:00
|
|
|
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
2012-01-04 16:26:30 +01:00
|
|
|
- (void) setSourceID: (NSString *) newSourceID
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2009-09-25 16:42:33 +02:00
|
|
|
- (NSString *) sourceID
|
|
|
|
{
|
|
|
|
return _sourceID;
|
|
|
|
}
|
|
|
|
|
2012-01-04 16:26:30 +01:00
|
|
|
- (void) setDisplayName: (NSString *) newDisplayName
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *) displayName
|
|
|
|
{
|
|
|
|
/* This method is only used when supporting user "source" addressbooks,
|
|
|
|
which is only supported by the LDAP backend for now. */
|
|
|
|
return _sourceID;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) setListRequiresDot: (BOOL) newListRequiresDot
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL) listRequiresDot
|
|
|
|
{
|
|
|
|
/* This method is not implemented for SQLSource. It must enable a mechanism
|
|
|
|
where using "." is not required to list the content of addressbooks. */
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* card editing */
|
|
|
|
- (void) setModifiers: (NSArray *) newModifiers
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2011-12-30 21:39:07 +01:00
|
|
|
- (NSArray *) modifiers
|
|
|
|
{
|
2012-01-04 16:26:30 +01:00
|
|
|
/* This method is only used when supporting card editing,
|
|
|
|
which is only supported by the LDAP backend for now. */
|
2011-12-30 21:39:07 +01:00
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSException *) addContactEntry: (NSDictionary *) roLdifRecord
|
|
|
|
withID: (NSString *) aId
|
|
|
|
{
|
|
|
|
NSString *reason;
|
|
|
|
|
|
|
|
reason = [NSString stringWithFormat: @"method '%@' is not available"
|
|
|
|
@" for class '%@'", NSStringFromSelector (_cmd),
|
|
|
|
NSStringFromClass (isa)];
|
|
|
|
|
|
|
|
return [NSException exceptionWithName: @"SQLSourceIOException"
|
|
|
|
reason: reason
|
|
|
|
userInfo: nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSException *) updateContactEntry: (NSDictionary *) roLdifRecord
|
|
|
|
{
|
|
|
|
NSString *reason;
|
|
|
|
|
|
|
|
reason = [NSString stringWithFormat: @"method '%@' is not available"
|
|
|
|
@" for class '%@'", NSStringFromSelector (_cmd),
|
|
|
|
NSStringFromClass (isa)];
|
|
|
|
|
|
|
|
return [NSException exceptionWithName: @"SQLSourceIOException"
|
|
|
|
reason: reason
|
|
|
|
userInfo: nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSException *) removeContactEntryWithID: (NSString *) aId
|
|
|
|
{
|
|
|
|
NSString *reason;
|
|
|
|
|
|
|
|
reason = [NSString stringWithFormat: @"method '%@' is not available"
|
|
|
|
@" for class '%@'", NSStringFromSelector (_cmd),
|
|
|
|
NSStringFromClass (isa)];
|
|
|
|
|
|
|
|
return [NSException exceptionWithName: @"SQLSourceIOException"
|
|
|
|
reason: reason
|
|
|
|
userInfo: nil];
|
|
|
|
}
|
|
|
|
|
2012-01-04 16:26:30 +01:00
|
|
|
/* user addressbooks */
|
|
|
|
- (BOOL) hasUserAddressBooks
|
|
|
|
{
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSArray *) addressBookSourcesForUser: (NSString *) user
|
|
|
|
{
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSException *) addAddressBookSource: (NSString *) newId
|
|
|
|
withDisplayName: (NSString *) newDisplayName
|
|
|
|
forUser: (NSString *) user
|
|
|
|
{
|
|
|
|
NSString *reason;
|
|
|
|
|
|
|
|
reason = [NSString stringWithFormat: @"method '%@' is not available"
|
|
|
|
@" for class '%@'", NSStringFromSelector (_cmd),
|
|
|
|
NSStringFromClass (isa)];
|
|
|
|
|
|
|
|
return [NSException exceptionWithName: @"SQLSourceIOException"
|
|
|
|
reason: reason
|
|
|
|
userInfo: nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSException *) renameAddressBookSource: (NSString *) newId
|
|
|
|
withDisplayName: (NSString *) newDisplayName
|
|
|
|
forUser: (NSString *) user
|
|
|
|
{
|
|
|
|
NSString *reason;
|
|
|
|
|
|
|
|
reason = [NSString stringWithFormat: @"method '%@' is not available"
|
|
|
|
@" for class '%@'", NSStringFromSelector (_cmd),
|
|
|
|
NSStringFromClass (isa)];
|
|
|
|
|
|
|
|
return [NSException exceptionWithName: @"SQLSourceIOException"
|
|
|
|
reason: reason
|
|
|
|
userInfo: nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSException *) removeAddressBookSource: (NSString *) newId
|
|
|
|
forUser: (NSString *) user
|
|
|
|
{
|
|
|
|
NSString *reason;
|
|
|
|
|
|
|
|
reason = [NSString stringWithFormat: @"method '%@' is not available"
|
|
|
|
@" for class '%@'", NSStringFromSelector (_cmd),
|
|
|
|
NSStringFromClass (isa)];
|
|
|
|
|
|
|
|
return [NSException exceptionWithName: @"SQLSourceIOException"
|
|
|
|
reason: reason
|
|
|
|
userInfo: nil];
|
|
|
|
}
|
|
|
|
|
2009-09-25 16:42:33 +02:00
|
|
|
@end
|