From a87bba64b2a25d993029e5b9511878464fbf539c Mon Sep 17 00:00:00 2001 From: Francis Lachapelle Date: Thu, 14 Apr 2011 18:41:10 +0000 Subject: [PATCH] Added support for the parameter "IMAPLoginMailField" for SQL/LDAP sources, and the parameter "authenticationFilter" for SQL sources. See ChangeLog. Monotone-Parent: 1cdaff22cdbdb961e1937dc8f1ac1936bd06dc99 Monotone-Revision: 36439821e42cfcb830bfff9081d7e1318f1e92ab Monotone-Author: flachapelle@inverse.ca Monotone-Date: 2011-04-14T18:41:10 Monotone-Branch: ca.inverse.sogo --- ChangeLog | 23 +++++++ SoObjects/SOGo/LDAPSource.h | 4 +- SoObjects/SOGo/LDAPSource.m | 18 +++++ SoObjects/SOGo/SOGoGCSFolder.m | 5 +- SoObjects/SOGo/SOGoUser.h | 1 + SoObjects/SOGo/SOGoUser.m | 9 +++ SoObjects/SOGo/SOGoUserManager.m | 27 +++++--- SoObjects/SOGo/SQLSource.h | 5 +- SoObjects/SOGo/SQLSource.m | 111 ++++++++++++++++++++++++------- 9 files changed, 169 insertions(+), 34 deletions(-) diff --git a/ChangeLog b/ChangeLog index a988d2183..12d5eb98f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,28 @@ 2011-04-14 Francis Lachapelle + * SoObjects/SOGo/SOGoUserManager.m (-getImapLoginForUID:): we now + consider the source parameter IMAPLoginFieldName. We continue to support + SOGoForceIMAPLoginWithEmail at the domain/system level. + + * SoObjects/SOGo/LDAPSource.m: we can now define a new parameter + named IMAPLoginFieldName to specify which field to use for IMAP + authentication. By default, we use the value of the UIDFieldName. + + * SoObjects/SOGo/SQLSource.m + (-checkLogin:password:perr:expire:grace:): if the defaults parameter + authenticationFilter is defined, we add it to the SQL where clause. + (_lookupContactEntry:considerEmail:): we add a new key + (canAuthenticate) to the returned dictionary that specifies if the + user can authenticate or not. We also add c_imaplogin when the + user must authenticate with a different username to the IMAP server. + + * SoObjects/SOGo/SOGoUser.m (-canAuthenticate): new method that + returns true if the user can authenticate, based on the + authentication filter associated to the source. + + * SoObjects/SOGo/SOGoGCSFolder.m (-ocsFolder): don't automatically + create the folder if the user can't authenticate. + * UI/Contacts/UIxContactsListActions.m: was UIxContactsListView.m. The new method contactsListAction now returns a JSON representation of the addressbook. diff --git a/SoObjects/SOGo/LDAPSource.h b/SoObjects/SOGo/LDAPSource.h index e77d5746a..2a0cc1085 100644 --- a/SoObjects/SOGo/LDAPSource.h +++ b/SoObjects/SOGo/LDAPSource.h @@ -4,6 +4,7 @@ * * Author: Wolfgang Sourdeau * Ludovic Marcotte + * Francis Lachapelle * * 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 @@ -53,7 +54,7 @@ NSString *CNField; NSString *UIDField; NSArray *mailFields, *searchFields; - NSString *IMAPHostField; + NSString *IMAPHostField, *IMAPLoginField; NSArray *bindFields; BOOL _bindAsCurrentUser; @@ -83,6 +84,7 @@ mailFields: (NSArray *) newMailFields searchFields: (NSArray *) newSearchFields IMAPHostField: (NSString *) newIMAPHostField + IMAPLoginField: (NSString *) newIMAPLoginField andBindFields: (id) newBindFields; - (NGLdapEntry *) lookupGroupEntryByUID: (NSString *) theUID; diff --git a/SoObjects/SOGo/LDAPSource.m b/SoObjects/SOGo/LDAPSource.m index d844f946a..9bb2d4dcb 100644 --- a/SoObjects/SOGo/LDAPSource.m +++ b/SoObjects/SOGo/LDAPSource.m @@ -4,6 +4,7 @@ * * Author: Wolfgang Sourdeau * Ludovic Marcotte + * Francis Lachapelle * * 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 @@ -161,6 +162,7 @@ static NSArray *commonSearchFields; searchFields = [NSArray arrayWithObjects: @"sn", @"displayname", @"telephonenumber", nil]; [searchFields retain]; IMAPHostField = nil; + IMAPLoginField = nil; bindFields = nil; _scope = @"sub"; _filter = nil; @@ -188,6 +190,7 @@ static NSArray *commonSearchFields; [mailFields release]; [searchFields release]; [IMAPHostField release]; + [IMAPLoginField release]; [bindFields release]; [_filter release]; [sourceID release]; @@ -223,6 +226,7 @@ static NSArray *commonSearchFields; mailFields: [udSource objectForKey: @"MailFieldNames"] searchFields: [udSource objectForKey: @"SearchFieldNames"] IMAPHostField: [udSource objectForKey: @"IMAPHostFieldName"] + IMAPLoginField: [udSource objectForKey: @"IMAPLoginFieldName"] andBindFields: [udSource objectForKey: @"bindFields"]]; if ([sourceDomain length]) @@ -305,6 +309,7 @@ static NSArray *commonSearchFields; mailFields: (NSArray *) newMailFields searchFields: (NSArray *) newSearchFields IMAPHostField: (NSString *) newIMAPHostField + IMAPLoginField: (NSString *) newIMAPLoginField andBindFields: (id) newBindFields { ASSIGN (baseDN, [newBaseDN lowercaseString]); @@ -316,6 +321,8 @@ static NSArray *commonSearchFields; ASSIGN (UIDField, newUIDField); if (newIMAPHostField) ASSIGN (IMAPHostField, newIMAPHostField); + if (newIMAPLoginField) + ASSIGN (IMAPLoginField, newIMAPLoginField); if (newMailFields) ASSIGN (mailFields, newMailFields); if (newSearchFields) @@ -694,6 +701,10 @@ static NSArray *commonSearchFields; // Add IMAP hostname from user defaults if ([IMAPHostField length]) [searchAttributes addObjectUniquely: IMAPHostField]; + + // Add IMAP login from user defaults + if ([IMAPLoginField length]) + [searchAttributes addObjectUniquely: IMAPLoginField]; } return searchAttributes; @@ -761,6 +772,13 @@ static NSArray *commonSearchFields; if ([ldapValue length] > 0) [contactEntry setObject: ldapValue forKey: @"c_imaphostname"]; } + + if (IMAPLoginField) + { + ldapValue = [[ldapEntry attributeWithName: IMAPLoginField] stringValueAtIndex: 0]; + if ([ldapValue length] > 0) + [contactEntry setObject: ldapValue forKey: @"c_imaplogin"]; + } } - (void) _fillConstraints: (NGLdapEntry *) ldapEntry diff --git a/SoObjects/SOGo/SOGoGCSFolder.m b/SoObjects/SOGo/SOGoGCSFolder.m index 0e9c2093e..3eb0c742d 100644 --- a/SoObjects/SOGo/SOGoGCSFolder.m +++ b/SoObjects/SOGo/SOGoGCSFolder.m @@ -480,14 +480,17 @@ static NSArray *childRecordFields = nil; - (GCSFolder *) ocsFolder { GCSFolder *folder; + SOGoUser *user; NSString *userLogin; if (!ocsFolder) { ocsFolder = [self ocsFolderForPath: [self ocsPath]]; - userLogin = [[context activeUser] login]; + user = [context activeUser]; + userLogin = [user login]; if (!ocsFolder && [userLogin isEqualToString: [self ownerInContext: context]] + && [user canAuthenticate] && [self folderIsMandatory] && [self create]) ocsFolder = [self ocsFolderForPath: [self ocsPath]]; diff --git a/SoObjects/SOGo/SOGoUser.h b/SoObjects/SOGo/SOGoUser.h index 00acf9962..6a2ed994a 100644 --- a/SoObjects/SOGo/SOGoUser.h +++ b/SoObjects/SOGo/SOGoUser.h @@ -112,6 +112,7 @@ - (NSMutableDictionary *) defaultIdentity; - (BOOL) isSuperUser; +- (BOOL) canAuthenticate; /* module access */ - (BOOL) canAccessModule: (NSString *) module; diff --git a/SoObjects/SOGo/SOGoUser.m b/SoObjects/SOGo/SOGoUser.m index 26097fc62..0e74924a8 100644 --- a/SoObjects/SOGo/SOGoUser.m +++ b/SoObjects/SOGo/SOGoUser.m @@ -757,6 +757,15 @@ return [[_domainDefaults superUsernames] containsObject: login]; } +- (BOOL) canAuthenticate +{ + id authValue; + + authValue = [self _fetchFieldForUser: @"canAuthenticate"]; + + return [authValue boolValue]; +} + /* module access */ - (BOOL) canAccessModule: (NSString *) module { diff --git a/SoObjects/SOGo/SOGoUserManager.m b/SoObjects/SOGo/SOGoUserManager.m index 5a19a6c41..5a4e13b56 100644 --- a/SoObjects/SOGo/SOGoUserManager.m +++ b/SoObjects/SOGo/SOGoUserManager.m @@ -337,17 +337,23 @@ - (NSString *) getImapLoginForUID: (NSString *) uid { NSDictionary *contactInfos; - NSString *domain; + NSString *domain, *login; SOGoDomainDefaults *dd; contactInfos = [self contactInfosForUserWithUIDorEmail: uid]; + login = [contactInfos objectForKey: @"c_imaplogin"]; domain = [contactInfos objectForKey: @"c_domain"]; - if ([domain length]) - dd = [SOGoDomainDefaults defaultsForDomain: domain]; - else - dd = [SOGoSystemDefaults sharedSystemDefaults]; - - return ([dd forceIMAPLoginWithEmail] ? [self getEmailForUID: uid] : uid); + if (login == nil) + { + if ([domain length]) + dd = [SOGoDomainDefaults defaultsForDomain: domain]; + else + dd = [SOGoSystemDefaults sharedSystemDefaults]; + + login = [dd forceIMAPLoginWithEmail] ? [self getEmailForUID: uid] : uid; + } + + return login; } - (NSString *) getUIDForEmail: (NSString *) email @@ -549,7 +555,7 @@ NSDictionary *userEntry; NSEnumerator *sogoSources; NSObject *currentSource; - NSString *sourceID, *cn, *c_domain, *c_uid, *c_imaphostname; + NSString *sourceID, *cn, *c_domain, *c_uid, *c_imaphostname, *c_imaplogin; NSArray *c_emails; BOOL access; @@ -558,6 +564,7 @@ c_uid = nil; c_domain = nil; c_imaphostname = nil; + c_imaplogin = nil; [currentUser setObject: [NSNumber numberWithBool: YES] forKey: @"CalendarAccess"]; @@ -583,6 +590,8 @@ [emails addObjectsFromArray: c_emails]; if (!c_imaphostname) c_imaphostname = [userEntry objectForKey: @"c_imaphostname"]; + if (!c_imaplogin) + c_imaplogin = [userEntry objectForKey: @"c_imaplogin"]; access = [[userEntry objectForKey: @"CalendarAccess"] boolValue]; if (!access) [currentUser setObject: [NSNumber numberWithBool: NO] @@ -603,6 +612,8 @@ if (c_imaphostname) [currentUser setObject: c_imaphostname forKey: @"c_imaphostname"]; + if (c_imaplogin) + [currentUser setObject: c_imaplogin forKey: @"c_imaplogin"]; [currentUser setObject: emails forKey: @"emails"]; [currentUser setObject: cn forKey: @"cn"]; [currentUser setObject: c_uid forKey: @"c_uid"]; diff --git a/SoObjects/SOGo/SQLSource.h b/SoObjects/SOGo/SQLSource.h index 18900cf5b..418399529 100644 --- a/SoObjects/SOGo/SQLSource.h +++ b/SoObjects/SOGo/SQLSource.h @@ -2,7 +2,8 @@ * * Copyright (C) 2009-2011 Inverse inc. * - * Author: Ludovic Marcotte + * Authors: Ludovic Marcotte + * Francis Lachapelle * * 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 @@ -36,7 +37,9 @@ { NSString *_sourceID; NSString *_domain; + NSString *_authenticationFilter; NSArray *_mailFields; + NSString *_imapLoginField; NSString *_userPasswordAlgorithm; NSURL *_viewURL; } diff --git a/SoObjects/SOGo/SQLSource.m b/SoObjects/SOGo/SQLSource.m index 9e8d653e4..1f21a948c 100644 --- a/SoObjects/SOGo/SQLSource.m +++ b/SoObjects/SOGo/SQLSource.m @@ -2,7 +2,8 @@ * * Copyright (C) 2009-2011 Inverse inc. * - * Author: Ludovic Marcotte + * Authors: Ludovic Marcotte + * Francis Lachapelle * * 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 @@ -31,9 +32,8 @@ #import #import +#import #import -#import -#import #import "SOGoConstants.h" #import "NSString+Utilities.h" @@ -79,6 +79,7 @@ if ((self = [super init])) { _sourceID = nil; + _authenticationFilter = nil; _mailFields = nil; _userPasswordAlgorithm = nil; _viewURL = nil; @@ -90,6 +91,7 @@ - (void) dealloc { [_sourceID release]; + [_authenticationFilter release]; [_mailFields release]; [_userPasswordAlgorithm release]; [_viewURL release]; @@ -103,8 +105,10 @@ self = [self init]; ASSIGN(_sourceID, [udSource objectForKey: @"id"]); + ASSIGN(_authenticationFilter, [udSource objectForKey: @"authenticationFilter"]); ASSIGN(_mailFields, [udSource objectForKey: @"MailFieldNames"]); ASSIGN(_userPasswordAlgorithm, [udSource objectForKey: @"userPasswordAlgorithm"]); + ASSIGN(_imapLoginField, [udSource objectForKey: @"IMAPLoginFieldName"]); if (!_userPasswordAlgorithm) _userPasswordAlgorithm = @"none"; @@ -203,9 +207,10 @@ grace: (int *) _grace { EOAdaptorChannel *channel; + EOQualifier *qualifier; GCSChannelManager *cm; NSException *ex; - NSString *sql; + NSMutableString *sql; BOOL rc; rc = NO; @@ -214,12 +219,25 @@ cm = [GCSChannelManager defaultChannelManager]; channel = [cm acquireOpenChannelForURL: _viewURL]; if (channel) - { - sql = [NSString stringWithFormat: (@"SELECT c_password" - @" FROM %@" - @" WHERE c_uid = '%@'"), - [_viewURL gcsTableName], _login]; - + { + qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"c_uid" + operatorSelector: EOQualifierOperatorEqual + value: _login]; + [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]; + ex = [channel evaluateExpressionX: sql]; if (!ex) { @@ -235,7 +253,8 @@ [channel cancelFetch]; } else - [self errorWithFormat: @"could not run SQL '%@': %@", sql, ex]; + [self errorWithFormat: @"could not run SQL '%@': %@", qualifier, ex]; + [cm releaseChannel: channel]; } else @@ -328,8 +347,10 @@ { NSMutableDictionary *response; EOAdaptorChannel *channel; + EOQualifier *qualifier; GCSChannelManager *cm; - NSString *sql, *value; + NSMutableString *sql; + NSString *value; NSException *ex; response = nil; @@ -340,21 +361,21 @@ if (channel) { if (!b) - sql = [NSString stringWithFormat: (@"SELECT *" - @" FROM %@" - @" WHERE c_uid = '%@'"), - [_viewURL gcsTableName], theID]; + sql = [NSMutableString stringWithFormat: (@"SELECT *" + @" FROM %@" + @" WHERE c_uid = '%@'"), + [_viewURL gcsTableName], theID]; else { - sql = [NSString stringWithFormat: (@"SELECT *" - @" FROM %@" - @" WHERE c_uid = '%@' OR" - @" LOWER(mail) = '%@'"), - [_viewURL gcsTableName], theID, [theID lowercaseString]]; - + sql = [NSMutableString stringWithFormat: (@"SELECT *" + @" FROM %@" + @" WHERE c_uid = '%@' OR" + @" LOWER(mail) = '%@'"), + [_viewURL gcsTableName], theID, [theID lowercaseString]]; + if (_mailFields && [_mailFields count] > 0) { - sql = [sql stringByAppendingString: [self _whereClauseFromArray: _mailFields value: [theID lowercaseString] exact: YES]]; + [sql appendString: [self _whereClauseFromArray: _mailFields value: [theID lowercaseString] exact: YES]]; } } @@ -397,6 +418,50 @@ } [response setObject: emails forKey: @"c_emails"]; + + // 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) + { + if ([response objectForKey: _imapLoginField]) + [response setObject: [response objectForKey: _imapLoginField] forKey: @"c_imaplogin"]; + } + } else [self errorWithFormat: @"could not run SQL '%@': %@", sql, ex];