diff --git a/.mtn-ignore b/.mtn-ignore index 6ee6e46e5..18a9ce018 100644 --- a/.mtn-ignore +++ b/.mtn-ignore @@ -21,3 +21,4 @@ SOPE/NGCards/samples/CardGroup.m SOPE/NGCards/samples/CardVersitRenderer.m SOPE/NGCards/samples/NGCardsSaxHandler.m Tests/Integration/config.py +Tests/Integration/teststrings diff --git a/ChangeLog b/ChangeLog index deffc70bb..e0fd7a001 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2010-12-28 Ludovic Marcotte + + * Implemented secured sessions. We no longer store in the + browser's cookie the base64 encoded version of username:password + You MUST now set OCSSessionsFolderURL to a value like: + postgresql://sogo:sogo@127.0.0.1:5432/sogo/sogo_sessions_folder + and the database table will be created automatically upon + next SOGo's startup. + 2010-12-23 Ludovic Marcotte * Added the /SOGo/so//Calendar/reloadWebCalendarsAndRedirect diff --git a/Documentation/SOGo Installation Guide.odt b/Documentation/SOGo Installation Guide.odt index 251f8b5d5..892f28eba 100644 Binary files a/Documentation/SOGo Installation Guide.odt and b/Documentation/SOGo Installation Guide.odt differ diff --git a/Main/SOGo.m b/Main/SOGo.m index 00d912042..04f16b262 100644 --- a/Main/SOGo.m +++ b/Main/SOGo.m @@ -232,10 +232,18 @@ static BOOL debugLeaks; } } - if (ok && [defaults enableEMailAlarms]) + if (ok) { fm = [GCSFolderManager defaultFolderManager]; - [[fm alarmsFolder] createFolderIfNotExists]; + + // Create the sessions table + [[fm sessionsFolder] createFolderIfNotExists]; + + // Create the email alarms table, if required + if ([defaults enableEMailAlarms]) + { + [[fm alarmsFolder] createFolderIfNotExists]; + } } return ok; diff --git a/SOPE/GDLContentStore/GCSFolderManager.h b/SOPE/GDLContentStore/GCSFolderManager.h index 6e395b5f0..3f4ca95c3 100644 --- a/SOPE/GDLContentStore/GCSFolderManager.h +++ b/SOPE/GDLContentStore/GCSFolderManager.h @@ -32,7 +32,7 @@ */ @class NSString, NSArray, NSURL, NSDictionary, NSException; -@class GCSChannelManager, GCSAlarmsFolder, GCSFolder, GCSFolderType; +@class GCSChannelManager, GCSAlarmsFolder, GCSFolder, GCSFolderType, GCSSessionsFolder; @interface GCSFolderManager : NSObject { @@ -73,6 +73,9 @@ /* alarms */ - (GCSAlarmsFolder *)alarmsFolder; +/* sessions */ +- (GCSSessionsFolder *)sessionsFolder; + /* folder types */ - (GCSFolderType *)folderTypeWithName:(NSString *)_name; diff --git a/SOPE/GDLContentStore/GCSFolderManager.m b/SOPE/GDLContentStore/GCSFolderManager.m index 45ccce026..6b998734d 100644 --- a/SOPE/GDLContentStore/GCSFolderManager.m +++ b/SOPE/GDLContentStore/GCSFolderManager.m @@ -1,14 +1,15 @@ /* + Copyright (C) 2006-2011 Inverse inc. Copyright (C) 2004-2007 SKYRIX Software AG - This file is part of OpenGroupware.org. + This file is part of SOGo. - OGo is free software; you can redistribute it and/or modify it under + 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. - OGo is distributed in the hope that it will be useful, but WITHOUT ANY + 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. @@ -42,6 +43,7 @@ #import "GCSAlarmsFolder.h" #import "GCSFolder.h" #import "GCSFolderType.h" +#import "GCSSessionsFolder.h" #import "GCSSpecialQueries.h" #import "NSURL+GCS.h" @@ -330,7 +332,12 @@ static NSCharacterSet *asciiAlphaNumericCS = nil; return [GCSAlarmsFolder alarmsFolderWithFolderManager: self]; } -/* path SQL */ + +/* sessions */ +- (GCSSessionsFolder *)sessionsFolder +{ + return [GCSSessionsFolder sessionsFolderWithFolderManager: self]; +} - (NSString *)generateSQLWhereForInternalNames:(NSArray *)_names exactMatch:(BOOL)_beExact orDirectSubfolderMatch:(BOOL)_directSubs diff --git a/SOPE/GDLContentStore/GCSSessionsFolder.h b/SOPE/GDLContentStore/GCSSessionsFolder.h new file mode 100644 index 000000000..5d0b86c55 --- /dev/null +++ b/SOPE/GDLContentStore/GCSSessionsFolder.h @@ -0,0 +1,56 @@ +/* GCSSessionsFolder.h - this file is part of SOGo + * + * Copyright (C) 2010-2011 Inverse inc. + * + * Author: Ludovic Marcotte + * + * 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. + */ + +#ifndef GCSSESSIONSFOLDER_H +#define GCSSESSIONSFOLDER_H + +#import + +@class NSCalendarDate; +@class NSException; +@class NSNumber; +@class NSString; + +@class GCSFolderManager; + +@interface GCSSessionsFolder : NSObject +{ + GCSFolderManager *folderManager; +} + ++ (id) sessionsFolderWithFolderManager: (GCSFolderManager *) newFolderManager; +- (void) setFolderManager: (GCSFolderManager *) newFolderManager; + +/* operations */ + +- (void) createFolderIfNotExists; +- (BOOL) canConnectStore; +- (NSDictionary *) recordForEntryWithID: (NSString *) theID; +- (void) deleteRecordForEntryWithID: (NSString *) theID; +- (void) writeRecordForEntryWithID: (NSString *) theID + value: (NSString *) theValue + creationDate: (NSCalendarDate *) theCreationDate + lastSeenDate: (NSCalendarDate *) theLastSeenDate; + +@end + +#endif /* GCSSESSIONSFOLDER_H */ diff --git a/SOPE/GDLContentStore/GCSSessionsFolder.m b/SOPE/GDLContentStore/GCSSessionsFolder.m new file mode 100644 index 000000000..08a614d92 --- /dev/null +++ b/SOPE/GDLContentStore/GCSSessionsFolder.m @@ -0,0 +1,357 @@ +/* GCSSessionsFolder.m - this file is part of SOGo + * + * Copyright (C) 2010-2011 Inverse inc. + * + * Author: Ludovic Marcotte + * + * 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 +#import +#import +#import +#import + +#import +#import + +#import +#import +#import +#import +#import + +#import "EOQualifier+GCS.h" +#import "GCSChannelManager.h" +#import "GCSFolderManager.h" +#import "GCSSpecialQueries.h" +#import "GCSStringFormatter.h" +#import "NSURL+GCS.h" + +#import "GCSSessionsFolder.h" + +static NSString *sessionsFolderURLString = nil; + +#warning GCSSessionsFolder should share a common ancestor with GCSFolder + +@implementation GCSSessionsFolder + ++ (void) initialize +{ + NSUserDefaults *ud; + + if (!sessionsFolderURLString) + { + ud = [NSUserDefaults standardUserDefaults]; + ASSIGN(sessionsFolderURLString, [ud stringForKey: @"OCSSessionsFolderURL"]); + } +} + ++ (id) sessionsFolderWithFolderManager: (GCSFolderManager *) newFolderManager +{ + GCSAlarmsFolder *newFolder; + + if (sessionsFolderURLString) + { + newFolder = [self new]; + [newFolder autorelease]; + [newFolder setFolderManager: newFolderManager]; + } + else + { + [self errorWithFormat: @"'OCSSessionsFolderURL' is not set"]; + newFolder = nil; + } + + return newFolder; +} + +- (void) setFolderManager: (GCSFolderManager *) newFolderManager +{ + ASSIGN (folderManager, newFolderManager); +} + +/* accessors */ + +- (NSURL *) _location +{ + NSURL *location; + + if (sessionsFolderURLString) + location = [NSURL URLWithString: sessionsFolderURLString]; + else + { + [self warnWithFormat: @"'OCSSessionsFolderURL' is not set"]; + location = nil; + } + + return location; +} + +- (GCSChannelManager *) _channelManager +{ + return [folderManager channelManager]; +} + +- (NSString *) _storeTableName +{ + return [[self _location] gcsTableName]; +} + +- (EOEntity *) _storeTableEntityForChannel: (EOAdaptorChannel *) tc +{ + static EOEntity *entity = nil; + EOAttribute *attribute; + NSString *tableName; + NSString *columns[] = { @"c_id", @"c_value", @"c_creationdate", + @"c_lastseen", nil }; + NSString **column; + NSMutableArray *keys; + NSDictionary *types; + + if (!entity) + { + entity = [EOEntity new]; + tableName = [self _storeTableName]; + [entity setName: tableName]; + [entity setExternalName: tableName]; + + types = [[tc specialQueries] sessionsAttributeTypes]; + + column = columns; + while (*column) + { + attribute = [EOAttribute new]; + [attribute setName: *column]; + [attribute setColumnName: *column]; + [attribute setExternalType: [types objectForKey: *column]]; + [entity addAttribute: attribute]; + [attribute release]; + column++; + } + + keys = [NSMutableArray arrayWithCapacity: 1]; + [keys addObject: [entity attributeNamed: @"c_id"]]; + [entity setPrimaryKeyAttributes: keys]; + + keys = [NSMutableArray arrayWithCapacity: 3]; + [keys addObject: [entity attributeNamed: @"c_value"]]; + [keys addObject: [entity attributeNamed: @"c_creationdate"]]; + [keys addObject: [entity attributeNamed: @"c_lastseen"]]; + [entity setClassProperties: keys]; + + [entity setAttributesUsedForLocking: [NSArray array]]; + } + + return entity; +} + +/* connection */ + +- (EOAdaptorChannel *) _acquireStoreChannel +{ + return [[self _channelManager] acquireOpenChannelForURL: [self _location]]; +} + +- (void) _releaseChannel: (EOAdaptorChannel *) _channel +{ + [[self _channelManager] releaseChannel:_channel]; +} + +- (BOOL) canConnectStore +{ + return [[self _channelManager] canConnect:[self _location]]; +} + +- (void) createFolderIfNotExists +{ + EOAdaptorChannel *tc; + NSString *sql, *tableName; + GCSSpecialQueries *queries; + + tc = [self _acquireStoreChannel]; + tableName = [self _storeTableName]; + + queries = [tc specialQueries]; + + sql = [NSString stringWithFormat: @"SELECT count(*) FROM %@", + [self _storeTableName]]; + if ([tc evaluateExpressionX: sql]) + { + sql = [queries createSessionsFolderWithName: tableName]; + if (![tc evaluateExpressionX: sql]) + [self logWithFormat: + @"sessions folder table '%@' successfully created!", + tableName]; + } + else + [tc cancelFetch]; + + [self _releaseChannel: tc]; +} + +/* operations */ + +- (NSDictionary *) recordForEntryWithID: (NSString *) theID +{ + EOAdaptorChannel *tc; + EOAdaptorContext *context; + NSString *tableName; + NSException *error; + NSArray *attrs; + NSDictionary *record; + EOEntity *entity; + EOSQLQualifier *qualifier; + + tableName = [self _storeTableName]; + + record = nil; + + tc = [self _acquireStoreChannel]; + if (tc) + { + context = [tc adaptorContext]; + entity = [self _storeTableEntityForChannel: tc]; + qualifier = [[EOSQLQualifier alloc] initWithEntity: entity + qualifierFormat: + @"c_id='%@'", + theID]; + [qualifier autorelease]; + + [context beginTransaction]; + error = [tc selectAttributesX: [entity attributesUsedForFetch] + describedByQualifier: qualifier + fetchOrder: nil + lock: NO]; + if (error) + [self errorWithFormat:@"%s: cannot execute fetch: %@", + __PRETTY_FUNCTION__, error]; + else + { + attrs = [tc describeResults: NO]; + record = [tc fetchAttributes: attrs withZone: NULL]; + [tc cancelFetch]; + } + [context rollbackTransaction]; + [self _releaseChannel: tc]; + } + + return record; +} + + + +- (NSDictionary *) _newRecordWithID: (NSString *) theID + value: (NSString *) theValue + creationDate: (NSCalendarDate *) theCreationDate + lastSeenDate: (NSCalendarDate *) theLastSeenDate +{ + NSNumber *cd, *ls; + + // We check if recId and alarmDate are nil prior calling -timeIntervalSince1970 + // Weird gcc optimizations can cause issue here. + cd = [NSNumber numberWithInt: (theCreationDate ? (int)[theCreationDate timeIntervalSince1970] : 0)]; + ls = [NSNumber numberWithInt: (theLastSeenDate ? (int)[theLastSeenDate timeIntervalSince1970] : 0)]; + + return [NSDictionary dictionaryWithObjectsAndKeys: theID, @"c_id", + theValue, @"c_value", + cd, @"c_creationdate", + ls, @"c_lastseen", + nil]; +} + +- (void) writeRecordForEntryWithID: (NSString *) theID + value: (NSString *) theValue + creationDate: (NSCalendarDate *) theCreationDate + lastSeenDate: (NSCalendarDate *) theLastSeenDate +{ + NSDictionary *record, *newRecord; + NSException *error; + EOAdaptorChannel *tc; + EOAdaptorContext *context; + EOEntity *entity; + EOSQLQualifier *qualifier; + + tc = [self _acquireStoreChannel]; + if (tc) + { + context = [tc adaptorContext]; + newRecord = [self _newRecordWithID: theID + value: theValue + creationDate: theCreationDate + lastSeenDate: theLastSeenDate]; + record = [self recordForEntryWithID: theID]; + entity = [self _storeTableEntityForChannel: tc]; + [context beginTransaction]; + if (record) + { + qualifier = [[EOSQLQualifier alloc] initWithEntity: entity + qualifierFormat: + @"c_id='%@'", + theID]; + [qualifier autorelease]; + error = [tc updateRowX: newRecord describedByQualifier: qualifier]; + } + else + error = [tc insertRowX: newRecord forEntity: entity]; + if (error) + { + [context rollbackTransaction]; + [self errorWithFormat:@"%s: cannot write record: %@", + __PRETTY_FUNCTION__, error]; + } + else + [context commitTransaction]; + [self _releaseChannel: tc]; + } +} + +- (void) deleteRecordForEntryWithID: (NSString *) theID +{ + EOAdaptorChannel *tc; + EOAdaptorContext *context; + EOEntity *entity; + EOSQLQualifier *qualifier; + NSException *error; + + tc = [self _acquireStoreChannel]; + if (tc) + { + context = [tc adaptorContext]; + entity = [self _storeTableEntityForChannel: tc]; + qualifier = [[EOSQLQualifier alloc] initWithEntity: entity + qualifierFormat: + @"c_id='%@'", + theID]; + [qualifier autorelease]; + [context beginTransaction]; + error = [tc deleteRowsDescribedByQualifierX: qualifier]; + if (error) + { + [context rollbackTransaction]; + [self errorWithFormat:@"%s: cannot delete record: %@", + __PRETTY_FUNCTION__, error]; + } + else + [context commitTransaction]; + [self _releaseChannel: tc]; + } +} + + + +@end diff --git a/SOPE/GDLContentStore/GCSSpecialQueries.h b/SOPE/GDLContentStore/GCSSpecialQueries.h index af406a844..08c2c3e69 100644 --- a/SOPE/GDLContentStore/GCSSpecialQueries.h +++ b/SOPE/GDLContentStore/GCSSpecialQueries.h @@ -37,6 +37,9 @@ - (NSString *) createFolderTableWithName: (NSString *) tableName; - (NSString *) createFolderACLTableWithName: (NSString *) tableName; +- (NSString *) createSessionsFolderWithName: (NSString *) tableName; +- (NSDictionary *) sessionsAttributeTypes; + @end @interface EOAdaptorChannel (GCSSpecialQueries) diff --git a/SOPE/GDLContentStore/GCSSpecialQueries.m b/SOPE/GDLContentStore/GCSSpecialQueries.m index 4effe6602..9bb476547 100644 --- a/SOPE/GDLContentStore/GCSSpecialQueries.m +++ b/SOPE/GDLContentStore/GCSSpecialQueries.m @@ -105,8 +105,26 @@ return nil; } +- (NSString *) createSessionsFolderWithName: (NSString *) tableName +{ + [self subclassResponsibility: _cmd]; + + return nil; +} + +- (NSDictionary *) sessionsAttributeTypes +{ + [self subclassResponsibility: _cmd]; + + return nil; +} + + @end +// +// PostgreSQL database +// @implementation GCSPostgreSQLSpecialQueries - (NSString *) createEMailAlarmsFolderWithName: (NSString *) tableName @@ -168,8 +186,39 @@ return [NSString stringWithFormat: sqlFolderACLFormat, tableName]; } +- (NSString *) createSessionsFolderWithName: (NSString *) tableName +{ + static NSString *sqlFolderFormat + = (@"CREATE TABLE %@ (" + @" c_id VARCHAR(255) NOT NULL," + @" c_value VARCHAR(255) NOT NULL," + @" c_creationdate INT4 NOT NULL," + @" c_lastseen INT4 NOT NULL)"); + + return [NSString stringWithFormat: sqlFolderFormat, tableName]; +} + +- (NSDictionary *) sessionsAttributeTypes +{ + static NSMutableDictionary *types = nil; + + if (!types) + { + types = [NSMutableDictionary new]; + [types setObject: @"varchar" forKey: @"c_id"]; + [types setObject: @"varchar" forKey: @"c_value"]; + [types setObject: @"int" forKey: @"c_creationdate"]; + [types setObject: @"int" forKey: @"c_lastseen"]; + } + + return types; +} + @end +// +// MySQL database +// @implementation GCSMySQLSpecialQueries - (NSString *) createEMailAlarmsFolderWithName: (NSString *) tableName @@ -231,8 +280,39 @@ return [NSString stringWithFormat: sqlFolderACLFormat, tableName]; } +- (NSString *) createSessionsFolderWithName: (NSString *) tableName +{ + static NSString *sqlFolderFormat + = (@"CREATE TABLE %@ (" + @" c_id VARCHAR(255) NOT NULL," + @" c_value VARCHAR(255) NOT NULL," + @" c_creationdate INT NOT NULL," + @" c_lastseen INT NOT NULL)"); + + return [NSString stringWithFormat: sqlFolderFormat, tableName]; +} + +- (NSDictionary *) sessionsAttributeTypes +{ + static NSMutableDictionary *types = nil; + + if (!types) + { + types = [NSMutableDictionary new]; + [types setObject: @"varchar" forKey: @"c_id"]; + [types setObject: @"varchar" forKey: @"c_value"]; + [types setObject: @"int" forKey: @"c_creationdate"]; + [types setObject: @"int" forKey: @"c_lastseen"]; + } + + return types; +} + @end +// +// Oracle database +// @implementation GCSOracleSpecialQueries - (NSString *) createEMailAlarmsFolderWithName: (NSString *) tableName @@ -293,4 +373,32 @@ return [NSString stringWithFormat: sqlFolderACLFormat, tableName]; } +- (NSString *) createSessionsFolderWithName: (NSString *) tableName +{ + static NSString *sqlFolderFormat + = (@"CREATE TABLE %@ (" + @" c_id VARCHAR2(255) NOT NULL," + @" c_value VARCHAR2(255) NOT NULL," + @" c_creationdate INTEGER NOT NULL," + @" c_lastseen INTEGER NOT NULL)"); + + return [NSString stringWithFormat: sqlFolderFormat, tableName]; +} + +- (NSDictionary *) sessionsAttributeTypes +{ + static NSMutableDictionary *types = nil; + + if (!types) + { + types = [NSMutableDictionary new]; + [types setObject: @"varchar2" forKey: @"c_id"]; + [types setObject: @"varchar2" forKey: @"c_value"]; + [types setObject: @"integer" forKey: @"c_creationdate"]; + [types setObject: @"integer" forKey: @"c_lastseen"]; + } + + return types; +} + @end diff --git a/SOPE/GDLContentStore/GNUmakefile b/SOPE/GDLContentStore/GNUmakefile index bb50fc57b..e0e50cab2 100644 --- a/SOPE/GDLContentStore/GNUmakefile +++ b/SOPE/GDLContentStore/GNUmakefile @@ -32,6 +32,7 @@ libGDLContentStore_HEADER_FILES += \ GCSFolderType.h \ GCSChannelManager.h \ GCSFieldExtractor.h \ + GCSSessionsFolder.h \ GCSSpecialQueries.h \ GCSStringFormatter.h \ @@ -48,6 +49,7 @@ libGDLContentStore_OBJC_FILES += \ GCSFolderType.m \ GCSChannelManager.m \ GCSFieldExtractor.m \ + GCSSessionsFolder.m \ GCSSpecialQueries.m \ GCSStringFormatter.m \ diff --git a/SoObjects/Mailer/SOGoMailAccount.m b/SoObjects/Mailer/SOGoMailAccount.m index 1d2845d66..d87c86fa2 100644 --- a/SoObjects/Mailer/SOGoMailAccount.m +++ b/SoObjects/Mailer/SOGoMailAccount.m @@ -629,14 +629,16 @@ static NSString *sieveScriptName = @"sogo"; return [NSMutableString string]; } +// +// Extract password from basic authentication. +// - (NSString *) imap4PasswordRenewed: (BOOL) renewed { - /* - Extract password from basic authentication. - */ - NSURL *imapURL; NSString *password; + NSURL *imapURL; + // Default account - ie., the account that is provided with a default + // SOGo installation. User-added IMAP accounts will have name >= 1. if ([nameInContainer isEqualToString: @"0"]) { imapURL = [self imap4URL]; diff --git a/SoObjects/SOGo/GNUmakefile b/SoObjects/SOGo/GNUmakefile index 8733c5b15..dee5233a6 100644 --- a/SoObjects/SOGo/GNUmakefile +++ b/SoObjects/SOGo/GNUmakefile @@ -46,6 +46,7 @@ SOGo_HEADER_FILES = \ NSURL+DAV.h \ \ SOGoAuthenticator.h \ + SOGoSession.h \ SOGoCASSession.h \ SOGoDAVAuthenticator.h \ SOGoProxyAuthenticator.h \ @@ -108,6 +109,7 @@ SOGo_OBJC_FILES = \ NSString+Utilities.m \ NSURL+DAV.m \ \ + SOGoSession.m \ SOGoCASSession.m \ SOGoDAVAuthenticator.m \ SOGoProxyAuthenticator.m \ diff --git a/SoObjects/SOGo/SOGoCache.h b/SoObjects/SOGo/SOGoCache.h index 7558f818b..ccbeb3be9 100644 --- a/SoObjects/SOGo/SOGoCache.h +++ b/SoObjects/SOGo/SOGoCache.h @@ -1,6 +1,6 @@ /* SOGoCache.h - this file is part of SOGo * - * Copyright (C) 2008-2010 Inverse inc. + * Copyright (C) 2008-2011 Inverse inc. * * Author: Wolfgang Sourdeau * Ludovic Marcotte @@ -92,7 +92,9 @@ forLogin: (NSString *) login; - (NSString *) userSettingsForLogin: (NSString *) theLogin; -/* CAS support */ +// +// CAS support +// - (NSString *) CASTicketFromIdentifier: (NSString *) identifier; - (NSString *) CASSessionWithTicket: (NSString *) ticket; - (void) setCASSession: (NSString *) casSession @@ -103,9 +105,11 @@ - (void) setCASPGTId: (NSString *) pgtId forPGTIOU: (NSString *) pgtIou; +// +// ACL caching support +// - (void) setACLs: (NSDictionary *) theACLs forPath: (NSString *) thePath; - - (NSMutableDictionary *) aclsForPath: (NSString *) thePath; @end diff --git a/SoObjects/SOGo/SOGoCache.m b/SoObjects/SOGo/SOGoCache.m index 805b12650..7a2d6c259 100644 --- a/SoObjects/SOGo/SOGoCache.m +++ b/SoObjects/SOGo/SOGoCache.m @@ -36,8 +36,13 @@ * +attributes value = NSMutableDictionary instance > user's LDAP attributes * +acl value = NSDictionary instance > ACLs on an object at specified path * + value = NSString instance (array components separated by ",") or group member logins for a specific group in domain + * cas-id:< > value = + * cas-ticket:< > value = + * cas-pgtiou:< > value = + * session:< > value = */ + #import #import #import @@ -437,7 +442,9 @@ static memcached_st *handle = NULL; return [self _valuesOfType: @"settings" forKey: theLogin]; } -/* CAS session support */ +// +// CAS session support +// - (NSString *) CASTicketFromIdentifier: (NSString *) identifier { return [self valueForKey: [NSString stringWithFormat: @"cas-id:%@", @@ -481,7 +488,7 @@ static memcached_st *handle = NULL; } // -// +// ACL caching code // - (void) setACLs: (NSDictionary *) theACLs forPath: (NSString *) thePath diff --git a/SoObjects/SOGo/SOGoDAVAuthenticator.m b/SoObjects/SOGo/SOGoDAVAuthenticator.m index a624d28d4..89684cec2 100644 --- a/SoObjects/SOGo/SOGoDAVAuthenticator.m +++ b/SoObjects/SOGo/SOGoDAVAuthenticator.m @@ -1,14 +1,15 @@ /* + Copyright (C) 2007-2011 Inverse inc. Copyright (C) 2004 SKYRIX Software AG - This file is part of OpenGroupware.org. + This file is part of SOGo. - OGo is free software; you can redistribute it and/or modify it under + 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. - OGo is distributed in the hope that it will be useful, but WITHOUT ANY + 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. diff --git a/SoObjects/SOGo/SOGoProxyAuthenticator.m b/SoObjects/SOGo/SOGoProxyAuthenticator.m index 1b6d532f1..93df9d43d 100644 --- a/SoObjects/SOGo/SOGoProxyAuthenticator.m +++ b/SoObjects/SOGo/SOGoProxyAuthenticator.m @@ -1,6 +1,6 @@ /* SOGoProxyAuthenticator.h - this file is part of SOGo * - * Copyright (C) 2009 Inverse inc. + * Copyright (C) 2009-2011 Inverse inc. * * Author: Wolfgang Sourdeau * diff --git a/SoObjects/SOGo/SOGoUserManager.m b/SoObjects/SOGo/SOGoUserManager.m index 0bc2ddb7f..4c463492c 100644 --- a/SoObjects/SOGo/SOGoUserManager.m +++ b/SoObjects/SOGo/SOGoUserManager.m @@ -402,7 +402,6 @@ grace: grace]; } - return checkOK; } diff --git a/SoObjects/SOGo/SOGoWebAuthenticator.m b/SoObjects/SOGo/SOGoWebAuthenticator.m index adcd280a1..9b202649f 100644 --- a/SoObjects/SOGo/SOGoWebAuthenticator.m +++ b/SoObjects/SOGo/SOGoWebAuthenticator.m @@ -1,6 +1,6 @@ /* SOGoWebAuthenticator.m - this file is part of SOGo * - * Copyright (C) 2007-2010 Inverse inc. + * Copyright (C) 2007-2011 Inverse inc. * * Author: Wolfgang Sourdeau * @@ -32,13 +32,16 @@ #import #import #import +#import #import #import +#import "SOGoCache.h" #import "SOGoCASSession.h" #import "SOGoConstants.h" #import "SOGoPermissions.h" +#import "SOGoSession.h" #import "SOGoSystemDefaults.h" #import "SOGoUser.h" #import "SOGoUserManager.h" @@ -59,12 +62,32 @@ - (BOOL) checkLogin: (NSString *) _login password: (NSString *) _pwd -{ +{ + NSString *username, *password, *value; SOGoPasswordPolicyError perr; int expire, grace; + - return [self checkLogin: _login - password: _pwd + // We check for the existence of the session in the database/memcache + // and we extract the real password from it. Here, + // + // _login == userKey + // _pwd == sessionKey + // + // If the session isn't present in the database, we fail the login process. + // + value = [SOGoSession valueForSessionKey: _pwd]; + + if (!value) + return NO; + + [SOGoSession decodeValue: value + usingKey: _login + login: &username + password: &password]; + + return [self checkLogin: username + password: password perr: &perr expire: &expire grace: &grace]; @@ -97,13 +120,16 @@ expire: _expire grace: _grace]; - // [self logWithFormat: @"Checked login with ppolicy enabled: %d %d %d", *_perr, *_expire, *_grace]; + //[self logWithFormat: @"Checked login with ppolicy enabled: %d %d %d", *_perr, *_expire, *_grace]; // It's important to return the real value here. The callee will handle // the return code and check for the _perr value. return rc; } +// +// +// - (SOGoUser *) userInContext: (WOContext *)_ctx { static SOGoUser *anonymous = nil; @@ -124,20 +150,64 @@ - (NSString *) passwordInContext: (WOContext *) context { - NSArray *creds; NSString *auth, *password; + NSArray *creds; auth = [[context request] cookieValueForKey: [self cookieNameInContext: context]]; creds = [self parseCredentials: auth]; if ([creds count] > 1) - password = [creds objectAtIndex: 1]; + { + NSString *login; + + [SOGoSession decodeValue: [SOGoSession valueForSessionKey: [creds objectAtIndex: 1]] + usingKey: [creds objectAtIndex: 0] + login: &login + password: &password]; + } else password = nil; return password; } +// +// We overwrite SOPE's method in order to proper retrieve +// the username from the cookie. +// +- (NSString *) checkCredentials: (NSString *)_creds +{ + NSString *login, *pwd, *userKey, *sessionKey; + NSArray *creds; + + SOGoPasswordPolicyError perr; + int expire, grace; + + if (![(creds = [self parseCredentials:_creds]) isNotEmpty]) + return nil; + + userKey = [creds objectAtIndex:0]; + if ([userKey isEqualToString:@"anonymous"]) + return @"anonymous"; + + sessionKey = [creds objectAtIndex:1]; + + [SOGoSession decodeValue: [SOGoSession valueForSessionKey: sessionKey] + usingKey: userKey + login: &login + password: &pwd]; + + if (![self checkLogin: login + password: pwd + perr: &perr + expire: &expire + grace: &grace]) + return nil; + + return login; +} + + - (NSString *) imapPasswordInContext: (WOContext *) context forServer: (NSString *) imapServer forceRenew: (BOOL) renew @@ -176,12 +246,12 @@ return [SOGoUser userWithLogin: login roles: roles]; } +// +// This is called by SoObjectRequestHandler prior doing any significant +// processing to allow the authenticator to reject invalid requests. +// - (WOResponse *) preprocessCredentialsInContext: (WOContext *) context { - /* - This is called by SoObjectRequestHandler prior doing any significant - processing to allow the authenticator to reject invalid requests. - */ WOResponse *response; NSString *auth; diff --git a/UI/MainUI/SOGoRootPage.m b/UI/MainUI/SOGoRootPage.m index 3d537eec8..7abff33f1 100644 --- a/UI/MainUI/SOGoRootPage.m +++ b/UI/MainUI/SOGoRootPage.m @@ -51,7 +51,7 @@ #import #import #import - +#import #import #import "SOGoRootPage.h" @@ -70,18 +70,37 @@ forAuthenticator: (SOGoWebAuthenticator *) auth { WOCookie *authCookie; - NSString *cookieValue, *cookieString, *appName; + NSString *cookieValue, *cookieString, *appName, *sessionKey, *userKey, *securedPassword; + // + // We create a new cookie - thus we create a new session + // associated to the user. For security, we generate: + // + // A- a session key + // B- a user key + // + // In memcached, the session key will be associated to the user's password + // which will be XOR'ed with the user key. + // + sessionKey = [SOGoSession generateKeyForLength: 16]; + userKey = [SOGoSession generateKeyForLength: 64]; + + NSString *value = [NSString stringWithFormat: @"%@:%@", username, password]; + securedPassword = [SOGoSession securedValue: value usingKey: userKey]; + + + [SOGoSession setValue: securedPassword forSessionKey: sessionKey]; + + //cookieString = [NSString stringWithFormat: @"%@:%@", + // username, password]; cookieString = [NSString stringWithFormat: @"%@:%@", - username, password]; + userKey, sessionKey]; cookieValue = [NSString stringWithFormat: @"basic %@", [cookieString stringByEncodingBase64]]; authCookie = [WOCookie cookieWithName: [auth cookieNameInContext: context] value: cookieValue]; appName = [[context request] applicationName]; [authCookie setPath: [NSString stringWithFormat: @"/%@/", appName]]; - /* enable this when we have code to determine whether request is HTTPS: - [authCookie setIsSecure: YES]; */ return authCookie; } @@ -107,7 +126,9 @@ return locationCookie; } -/* actions */ +// +// +// - (WOResponse *) _responseWithLDAPPolicyError: (int) error { NSDictionary *jsonError; @@ -164,8 +185,9 @@ response = [self responseWithStatus: 200 andJSONRepresentation: json]; - authCookie = [self _cookieWithUsername: username andPassword: password - forAuthenticator: auth]; + authCookie = [self _cookieWithUsername: username + andPassword: password + forAuthenticator: auth]; [response addCookie: authCookie]; supportedLanguages = [[SOGoSystemDefaults sharedSystemDefaults] @@ -369,11 +391,6 @@ return [[SOGoSystemDefaults sharedSystemDefaults] supportedLanguages]; } -// - (NSString *) language -// { -// return [SOGoUser language]; -// } - - (NSString *) languageText { NSString *text; @@ -397,7 +414,7 @@ - (WOResponse *) changePasswordAction { - NSString *username, *password, *newPassword; + NSString *username, *password, *newPassword, *value; SOGoUserManager *um; SOGoPasswordPolicyError error; WOResponse *response; @@ -405,11 +422,22 @@ NSDictionary *message; SOGoWebAuthenticator *auth; WOCookie *authCookie; + NSArray *creds; request = [context request]; message = [[request contentAsString] objectFromJSONString]; - username = [message objectForKey: @"userName"]; - password = [message objectForKey: @"password"]; + + auth = [[WOApplication application] + authenticatorInContext: context]; + value = [[context request] + cookieValueForKey: [auth cookieNameInContext: context]]; + creds = [auth parseCredentials: value]; + + [SOGoSession decodeValue: [SOGoSession valueForSessionKey: [creds objectAtIndex: 1]] + usingKey: [creds objectAtIndex: 0] + login: &username + password: &password]; + newPassword = [message objectForKey: @"newPassword"]; um = [SOGoUserManager sharedUserManager]; @@ -420,9 +448,10 @@ newPassword: newPassword perr: &error]) { + // We delete the previous session + [SOGoSession deleteValueForSessionKey: [creds objectAtIndex: 1]]; + response = [self responseWith204]; - auth = [[WOApplication application] - authenticatorInContext: context]; authCookie = [self _cookieWithUsername: username andPassword: newPassword forAuthenticator: auth]; diff --git a/UI/MainUI/SOGoUserHomePage.m b/UI/MainUI/SOGoUserHomePage.m index 1c2950a5e..56d36d5bf 100644 --- a/UI/MainUI/SOGoUserHomePage.m +++ b/UI/MainUI/SOGoUserHomePage.m @@ -41,6 +41,7 @@ #import #import #import +#import #import #import #import @@ -275,11 +276,13 @@ - (id ) logoffAction { + SOGoWebAuthenticator *auth; + NSString *userName, *value; WOResponse *response; - WOCookie *cookie; NSCalendarDate *date; - NSString *userName; - + WOCookie *cookie; + NSArray *creds; + userName = [[context activeUser] login]; [self logWithFormat: @"user '%@' logged off", userName]; @@ -288,6 +291,18 @@ date = [NSCalendarDate calendarDate]; [date setTimeZone: [NSTimeZone timeZoneWithAbbreviation: @"GMT"]]; + // We cleanup the memecached/database session cache. We do this before + // invoking _logoutCookieWithDate: in order to obtain its value. + auth = [[self clientObject] authenticatorInContext: context]; + if ([auth respondsToSelector: @selector (cookieNameInContext:)]) + { + value = [[context request] cookieValueForKey: [auth cookieNameInContext: context]]; + creds = [auth parseCredentials: value]; + + if ([creds count] > 1) + [SOGoSession deleteValueForSessionKey: [creds objectAtIndex: 1]]; + } + cookie = [self _logoutCookieWithDate: date]; if (cookie) [response addCookie: cookie]; @@ -298,6 +313,7 @@ forKey: @"Cache-Control"]; [response setHeader: @"no-cache" forKey: @"Pragma"]; + return response; } diff --git a/UI/WebServerResources/SOGoRootPage.js b/UI/WebServerResources/SOGoRootPage.js index cc58cfd42..e335ed67f 100644 --- a/UI/WebServerResources/SOGoRootPage.js +++ b/UI/WebServerResources/SOGoRootPage.js @@ -98,6 +98,7 @@ function onLoginCallback(http) { if (http.status == 200) { // Make sure browser's cookies are enabled var loginCookie = readLoginCookie(); + if (!loginCookie) { SetLogMessage("errorMessage", _("cookiesNotEnabled")); submitBtn.disabled = false;