diff --git a/ChangeLog b/ChangeLog index 6855ce4cb..c6a9f669f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2009-04-30 Ludovic Marcotte + + * Added SoObjects/SOGo/SOGoGroup.{h.m} and + modified the LDAPSource, LDAPUserManager, SOGoAppoinmentObject + in order to add initial groups support which decomposes + groups to members when events are saved. + 2009-04-27 Francis Lachapelle * UI/Scheduler/UIxCalListingActions.m ([UIxCalListingActions diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index bbc57bc45..28bc112a1 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -54,12 +54,10 @@ #import #import -// #import #import #import #import #import -// #import #import #import #import diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m index a840390b3..79b3a0d19 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SoObjects/Appointments/SOGoAppointmentObject.m @@ -1,15 +1,15 @@ /* Copyright (C) 2004-2005 SKYRIX Software AG - Copyright (C) 2007-2008 Inverse inc. + Copyright (C) 2007-2009 Inverse inc. This file is part of OpenGroupware.org. - 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. @@ -40,6 +40,7 @@ #import #import #import +#import #import #import #import @@ -395,24 +396,92 @@ toAttendees: updateAttendees]; } -- (void) _handleAddedUsers: (NSArray *) attendees +// +// Returns "YES" if a group was decomposed during attendees addition. +// +- (BOOL) _handleAddedUsers: (NSArray *) attendees fromEvent: (iCalEvent *) newEvent { + NSMutableArray *array, *attendeesFromGroups; NSEnumerator *enumerator; iCalPerson *currentAttendee; NSString *currentUID; + BOOL b; + int i; + + array = [NSMutableArray arrayWithArray: [newEvent attendees]]; + attendeesFromGroups = [NSMutableArray array]; + RETAIN(attendees); + b = NO; enumerator = [attendees objectEnumerator]; while ((currentAttendee = [enumerator nextObject])) { - currentUID = [currentAttendee uid]; - if (currentUID) + SOGoGroup *group; + + group = [SOGoGroup groupWithIdentifier: [currentAttendee rfc822Email]]; + + if (group) + { + iCalPerson *person; + NSArray *members; + SOGoUser *user; + + // We did decompose a group... + [array removeObject: currentAttendee]; + b = YES; + + members = [group members]; + for (i = 0; i < [members count]; i++) + { + user = [members objectAtIndex: i]; + + // If the organizer is part of the group, we skip it from + // the addition to the attendees' list + if ([user hasEmail: [[newEvent organizer] rfc822Email]]) + continue; + + person = [self iCalPersonWithUID: [user login]]; + [person setTag: @"ATTENDEE"]; + [person setParticipationStatus: iCalPersonPartStatNeedsAction]; + [person setRsvp: @"TRUE"]; + [person setRole: @"REQ-PARTICIPANT"]; + + [attendeesFromGroups addObject: [user login]]; + + if (![array containsObject: person]) + [array addObject: person]; + } + } + else + { + currentUID = [currentAttendee uid]; + if (currentUID) + [self _addOrUpdateEvent: newEvent + forUID: currentUID + owner: owner]; + } + } + + if (b) + { + NSLog(@"New attendees: %@", array); + [newEvent setAttendees: array]; + + for (i = 0; i < [attendeesFromGroups count]; i++) [self _addOrUpdateEvent: newEvent - forUID: currentUID + forUID: [attendeesFromGroups objectAtIndex: i] owner: owner]; } + + RELEASE(attendees); + + return b; } +// +// +// - (void) _handleUpdatedEvent: (iCalEvent *) newEvent fromOldEvent: (iCalEvent *) oldEvent { @@ -467,8 +536,19 @@ if ([attendees count]) { + NSArray *originalAttendees; + + originalAttendees = [NSArray arrayWithArray: [newEvent attendees]]; + // Send an invitation to new attendees - [self _handleAddedUsers: attendees fromEvent: newEvent]; + if ([self _handleAddedUsers: attendees fromEvent: newEvent]) + { + // We need to compute our new set for the invitation template + // if we decomposed groups. + attendees = [NSMutableArray arrayWithArray: [newEvent attendees]]; + [(NSMutableArray *)attendees removeObjectsInArray: originalAttendees]; + } + [self sendEMailUsingTemplateNamed: @"Invitation" forObject: [newEvent itipEntryWithMethod: @"request"] previousObject: oldEvent @@ -499,7 +579,13 @@ attendees = [newEvent attendeesWithoutUser: ownerUser]; if ([attendees count]) { - [self _handleAddedUsers: attendees fromEvent: newEvent]; + if ([self _handleAddedUsers: attendees fromEvent: newEvent]) + { + // We refetch the list of attendees and save again the + // event as a group was decomposed + attendees = [newEvent attendeesWithoutUser: ownerUser]; + [super saveComponent: newEvent]; + } [self sendEMailUsingTemplateNamed: @"Invitation" forObject: [newEvent itipEntryWithMethod: @"request"] previousObject: nil @@ -529,7 +615,8 @@ } [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent]; - // The sequence has possibly been increased -- resave the event + // The sequence has possibly been increased -- resave the event. + // This will also take care of a decomposed group. [super saveComponent: newEvent]; } } @@ -1224,4 +1311,84 @@ return @"IPM.Appointment"; } +// +// If we see "X-SOGo: NoGroupsDecomposition" in the HTTP headers, we +// simply invoke super's PUTAction. +// +- (id) PUTAction: (WOContext *) _ctx +{ + iCalPerson *currentAttendee; + NSEnumerator *enumerator; + iCalCalendar *calendar; + NSMutableArray *array; + NSArray *allEvents; + SOGoGroup *group; + iCalEvent *event; + WORequest *rq; + int i; + + rq = [_ctx request]; + + //NSLog(@"Content from request: %@", [rq contentAsString]); + + // The algorithm is pretty straightforward: + // + // We get all events + // We get all attendees + // If some are groups, we decompose them + // We regenerate the iCalendar string + // + calendar = [iCalCalendar parseSingleFromSource: [rq contentAsString]]; + allEvents = [calendar events]; + + for (i = 0; i < [allEvents count]; i++) + { + event = [allEvents objectAtIndex: i]; + array = [NSMutableArray arrayWithArray: [event attendees]]; + + enumerator = [[event attendees] objectEnumerator]; + while ((currentAttendee = [enumerator nextObject])) + { + group = [SOGoGroup groupWithIdentifier: [currentAttendee rfc822Email]]; + + if (group) + { + iCalPerson *person; + NSArray *members; + SOGoUser *user; + + // We did decompose a group... + [array removeObject: currentAttendee]; + + members = [group members]; + for (i = 0; i < [members count]; i++) + { + user = [members objectAtIndex: i]; + + // If the organizer is part of the group, we skip it from + // the addition to the attendees' list + if ([user hasEmail: [[event organizer] rfc822Email]]) + continue; + + person = [self iCalPersonWithUID: [user login]]; + [person setTag: @"ATTENDEE"]; + [person setParticipationStatus: iCalPersonPartStatNeedsAction]; + [person setRsvp: @"TRUE"]; + [person setRole: @"REQ-PARTICIPANT"]; + + if (![array containsObject: person]) + [array addObject: person]; + } + } + } + + [event setAttendees: array]; + } + + //NSLog(@"Content from calendar:secure: %@", [calendar versitString]); + + [rq setContent: [[calendar versitString] dataUsingEncoding: [rq contentEncoding]]]; + return [super PUTAction: _ctx]; +} + @end /* SOGoAppointmentObject */ diff --git a/SoObjects/Appointments/SOGoCalendarComponent.m b/SoObjects/Appointments/SOGoCalendarComponent.m index 82cc7899b..742611ccb 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.m +++ b/SoObjects/Appointments/SOGoCalendarComponent.m @@ -1,6 +1,6 @@ /* SOGoCalendarComponent.m - this file is part of SOGo * - * Copyright (C) 2006-2008 Inverse inc. + * Copyright (C) 2006-2009 Inverse inc. * * Author: Wolfgang Sourdeau * diff --git a/SoObjects/SOGo/GNUmakefile b/SoObjects/SOGo/GNUmakefile index 2bcc855d6..e5e431515 100644 --- a/SoObjects/SOGo/GNUmakefile +++ b/SoObjects/SOGo/GNUmakefile @@ -49,6 +49,7 @@ SOGo_HEADER_FILES = \ SOGoWebDAVAclManager.h \ SOGoWebDAVValue.h \ SOGoMailer.h \ + SOGoGroup.h \ SOGoUser.h \ \ NSDictionary+BSJSONAdditions.h \ @@ -94,6 +95,7 @@ SOGo_OBJC_FILES = \ SOGoWebDAVAclManager.m \ SOGoWebDAVValue.m \ SOGoMailer.m \ + SOGoGroup.m \ SOGoUser.m \ \ NSDictionary+BSJSONAdditions.m \ diff --git a/SoObjects/SOGo/LDAPSource.h b/SoObjects/SOGo/LDAPSource.h index ab1f35a8a..0d9f2b084 100644 --- a/SoObjects/SOGo/LDAPSource.h +++ b/SoObjects/SOGo/LDAPSource.h @@ -28,6 +28,7 @@ @class NSDictionary; @class NSString; @class NGLdapConnection; +@class NGLdapEntry; @interface LDAPSource : NSObject { @@ -62,6 +63,7 @@ hostname: (NSString *) newBindHostname port: (NSString *) newBindPort encryption: (NSString *) newEncryption; + - (void) setBaseDN: (NSString *) newBaseDN IDField: (NSString *) newIDField CNField: (NSString *) newCNField @@ -69,11 +71,15 @@ mailFields: (NSArray *) newMailFields andBindFields: (NSString *) newBindFields; +- (NSString *) loginForDN: (NSString *) theDN; + - (BOOL) checkLogin: (NSString *) login andPassword: (NSString *) password; - (NSDictionary *) lookupContactEntry: (NSString *) theID; - (NSDictionary *) lookupContactEntryWithUIDorEmail: (NSString *) entryID; +- (NGLdapEntry *) lookupGroupEntry: (NSString *) theID; + - (NSArray *) allEntryIDs; - (NSArray *) fetchContactsMatching: (NSString *) filter; - (NSString *) sourceID; diff --git a/SoObjects/SOGo/LDAPSource.m b/SoObjects/SOGo/LDAPSource.m index ea7b737d5..c7741c7fc 100644 --- a/SoObjects/SOGo/LDAPSource.m +++ b/SoObjects/SOGo/LDAPSource.m @@ -377,6 +377,21 @@ static NSLock *lock; return userDN; } +- (NSString *) loginForDN: (NSString *) theDN +{ + NGLdapEntry *entry; + + entry = [ldapConnection entryAtDN: theDN + attributes: [NSArray arrayWithObject: IDField]]; + + if (entry) + { + return [[entry attributeWithName: IDField] stringValueAtIndex: 0]; + } + + return nil; +} + - (BOOL) checkLogin: (NSString *) loginToCheck andPassword: (NSString *) passwordToCheck { @@ -822,6 +837,61 @@ static NSLock *lock; return contactEntry; } +// Use the email address for now... +- (NGLdapEntry *) lookupGroupEntry: (NSString *) theID +{ + NGLdapEntry *ldapEntry; + +#if defined(THREADSAFE) + [lock lock]; +#endif + + ldapEntry = nil; + + if ([theID length] > 0) + { + if ([self _initLDAPConnection]) + { + NSMutableArray *attributes; + NSEnumerator *entries; + EOQualifier *qualifier; + NSString *s; + + // FIXME + s = [NSString stringWithFormat: @"(mail='%@')", theID]; + qualifier = [EOQualifier qualifierWithQualifierFormat: s]; + + // We look for additional attributes - the ones related to group membership + attributes = [NSMutableArray arrayWithArray: [self _searchAttributes]]; + [attributes addObject: @"member"]; + [attributes addObject: @"memberOf"]; + + if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame) + entries = [ldapConnection baseSearchAtBaseDN: baseDN + qualifier: qualifier + attributes: attributes]; + else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame) + entries = [ldapConnection flatSearchAtBaseDN: baseDN + qualifier: qualifier + attributes: attributes]; + else + entries = [ldapConnection deepSearchAtBaseDN: baseDN + qualifier: qualifier + attributes: attributes]; + + ldapEntry = [entries nextObject]; + } + + [ldapConnection autorelease]; + } + +#if defined(THREADSAFE) + [lock unlock]; +#endif + + return ldapEntry; +} + - (NSString *) sourceID { return sourceID; diff --git a/SoObjects/SOGo/LDAPUserManager.h b/SoObjects/SOGo/LDAPUserManager.h index d62e6d907..7e84c4aec 100644 --- a/SoObjects/SOGo/LDAPUserManager.h +++ b/SoObjects/SOGo/LDAPUserManager.h @@ -29,6 +29,7 @@ @class NSMutableDictionary; @class NSString; @class NSTimer; +@class NGLdapEntry; @class LDAPSource; diff --git a/SoObjects/SOGo/LDAPUserManager.m b/SoObjects/SOGo/LDAPUserManager.m index 72f79182f..11ccd6588 100644 --- a/SoObjects/SOGo/LDAPUserManager.m +++ b/SoObjects/SOGo/LDAPUserManager.m @@ -164,6 +164,7 @@ static NSLock *lock = nil; - (void) dealloc { [sources release]; + [sourcesMetadata release]; [super dealloc]; } diff --git a/SoObjects/SOGo/SOGoFolder.m b/SoObjects/SOGo/SOGoFolder.m index ddec5b493..823d08ca7 100644 --- a/SoObjects/SOGo/SOGoFolder.m +++ b/SoObjects/SOGo/SOGoFolder.m @@ -1,6 +1,6 @@ /* SOGoFolder.m - this file is part of SOGo * - * Copyright (C) 2007 Inverse inc. + * Copyright (C) 2007-2009 Inverse inc. * * Author: Wolfgang Sourdeau * diff --git a/SoObjects/SOGo/SOGoGroup.h b/SoObjects/SOGo/SOGoGroup.h new file mode 100644 index 000000000..1bf5764ff --- /dev/null +++ b/SoObjects/SOGo/SOGoGroup.h @@ -0,0 +1,47 @@ +/* SOGoGroup.h - this file is part of SOGo + * + * Copyright (C) 2009 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 __SOGOGROUP_H__ +#define __SOGOGROUP_H__ + +#import + +@class LDAPSource; +@class NSArray; +@class NSString; +@class NGLdapEntry; + +@interface SOGoGroup : NSObject +{ + @private + NSString *_identifier; + NGLdapEntry *_entry; + LDAPSource *_source; +} + ++ (id) groupWithIdentifier: (NSString *) theID; + +- (NSArray *) members; + +@end + +#endif // __SOGOGROUP_H__ diff --git a/SoObjects/SOGo/SOGoGroup.m b/SoObjects/SOGo/SOGoGroup.m new file mode 100644 index 000000000..61c6dd6dd --- /dev/null +++ b/SoObjects/SOGo/SOGoGroup.m @@ -0,0 +1,215 @@ +/* SOGoGroup.m - this file is part of SOGo + * + * Copyright (C) 2009 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. + */ + +/* + Here are some group samples: + + + [ POSIX group ] + + dn: cn=it-staff,ou=Group,dc=zzz,dc=xxx,dc=yyy + objectClass: posixGroup + objectClass: top + cn: it-staff + userPassword: {crypt}x + gidNumber: 8000 + memberUid: lsa + memberUid: mrm + memberUid: ij + memberUid: no + memberUid: ld + memberUid: db + memberUid: rgl + memberUid: ja + memberUid: hbt + memberUid: hossein + + */ + +#include "SOGoGroup.h" + +#import +#import + +#include "LDAPSource.h" +#include "LDAPUserManager.h" +#include "SOGoUser.h" + +#import +#import +#import + +@implementation SOGoGroup + +- (id) initWithIdentifier: (NSString *) theID + source: (LDAPSource *) theSource + entry: (NGLdapEntry *) theEntry +{ + self = [super init]; + + if (self) + { + ASSIGN(_identifier, theID); + ASSIGN(_source, theSource); + ASSIGN(_entry, theEntry); + } + + return self; +} + +- (void) dealloc +{ + RELEASE(_identifier); + RELEASE(_source); + RELEASE(_entry); + + [super dealloc]; +} + +// +// Returns nil if theID (which is an email address) doesn't +// actually match to a group (so its objectClass isn't really a group) +// ++ (id) groupWithIdentifier: (NSString *) theID +{ + NSArray *allSources; + NGLdapEntry *entry; + LDAPSource *source; + id o; + + int i; + + // Don't bother looking in all sources if the + // supplied email address is nil. + if (!theID) + return nil; + + allSources = [[LDAPUserManager sharedUserManager] sourceIDs]; + o = nil; + + for (i = 0; i < [allSources count]; i++) + { + source = [[LDAPUserManager sharedUserManager] sourceWithID: [allSources objectAtIndex: i]]; + entry = [source lookupGroupEntry: theID]; + + if (entry) + break; + + entry = nil; + } + + if (entry) + { + NSArray *classes; + + // We check to see if it's a group + classes = [[entry attributeWithName: @"objectClass"] allStringValues]; + NSLog(@"classes = %@", classes); + + // Found a group, let's return it. + if ([classes containsObject: @"group"] || + [classes containsObject: @"groupOfNames"] || + [classes containsObject: @"groupOfUniqueNames"] || + [classes containsObject: @"posixGroup"]) + { + o = [[self alloc] initWithIdentifier: theID + source: source + entry: entry]; + AUTORELEASE(o); + } + } + + return o; +} + +// +// This method actually try to obtain all members +// from either dynamic of static groups. +// +- (NSArray *) members +{ + NSMutableArray *dns, *uids; + NSMutableArray *array; + NSString *login; + SOGoUser *user; + NSArray *o; + int i, c; + + array = [NSMutableArray array]; + uids = [NSMutableArray array]; + dns = [NSMutableArray array]; + + // We check if it's a static group + NSLog(@"attributes = %@", [_entry attributes]); + + // Fetch "members" - we get DNs + o = [[_entry attributeWithName: @"member"] allStringValues]; + if (o) [dns addObjectsFromArray: o]; + + // Fetch "uniqueMembers" - we get DNs + o = [[_entry attributeWithName: @"uniqueMember"] allStringValues]; + if (o) [dns addObjectsFromArray: o]; + + // Fetch "memberUid" - we get UID (like login names) + o = [[_entry attributeWithName: @"memberUid"] allStringValues]; + if (o) [uids addObjectsFromArray: o]; + + c = [dns count] + [uids count]; + + NSLog(@"members count (static group): %d", c); + + // We deal with a static group, let's add the members + if (c) + { + // We add members for whom we have their associated DN + for (i = 0; i < [dns count]; i++) + { + login = [_source loginForDN: [dns objectAtIndex: i]]; + NSLog(@"member = %@", login); + user = [SOGoUser userWithLogin: login roles: nil]; + + if (user) + [array addObject: user]; + } + + // We add members for whom we have their associated login name + for (i = 0; i < [uids count]; i++) + { + login = [uids objectAtIndex: i]; + NSLog(@"member = %@", login); + user = [SOGoUser userWithLogin: login roles: nil]; + + if (user) + [array addObject: user]; + } + } + else + { + // We deal with a dynamic group, let's search all users for whom + // memberOf is equal to our group's DN. + // We also need to look for labelelURI? + } + + return array; +} + +@end