1/* SOGoGroup.m - this file is part of SOGo 2 * 3 * Copyright (C) 2009-2014 Inverse inc. 4 * 5 * This file is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2, or (at your option) 8 * any later version. 9 * 10 * This file is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; see the file COPYING. If not, write to 17 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 18 * Boston, MA 02111-1307, USA. 19 */ 20 21/* 22 Here are some group samples: 23 24 [ POSIX group ] 25 26 dn: cn=it-staff,ou=Group,dc=zzz,dc=xxx,dc=yyy 27 objectClass: posixGroup 28 objectClass: top 29 cn: it-staff 30 userPassword: {crypt}x 31 gidNumber: 8000 32 memberUid: lsa 33 memberUid: mrm 34 memberUid: ij 35 memberUid: no 36 memberUid: ld 37 memberUid: db 38 memberUid: rgl 39 memberUid: ja 40 memberUid: hbt 41 memberUid: hossein 42 43 dn: cn=inverse,ou=groups,dc=inverse,dc=ca 44 objectClass: groupOfUniqueNames 45 objectClass: top 46 objectClass: extensibleObject 47 uniqueMember: uid=flachapelle,ou=users,dc=inverse,dc=ca 48 uniqueMember: uid=lmarcotte,ou=users,dc=inverse,dc=ca 49 uniqueMember: uid=wsourdeau,ou=users,dc=inverse,dc=ca 50 cn: inverse 51 mail: inverse@inverse.ca 52 53 */ 54 55#include "SOGoGroup.h" 56 57#import <Foundation/NSArray.h> 58#import <Foundation/NSAutoreleasePool.h> 59#import <Foundation/NSDictionary.h> 60#import <Foundation/NSString.h> 61 62#include "SOGoCache.h" 63#include "SOGoSource.h" 64#include "SOGoSystemDefaults.h" 65#include "SOGoUserManager.h" 66#include "SOGoUser.h" 67 68#import <NGLdap/NGLdapConnection.h> 69#import <NGLdap/NGLdapAttribute.h> 70#import <NGLdap/NGLdapEntry.h> 71 72#define CHECK_CLASS(o) ({ \ 73 if ([o isKindOfClass: [NSString class]]) \ 74 o = [NSArray arrayWithObject: o]; \ 75}) 76 77@implementation SOGoGroup 78 79- (id) initWithIdentifier: (NSString *) theID 80 domain: (NSString *) theDomain 81 source: (NSObject <SOGoSource> *) theSource 82 entry: (NGLdapEntry *) theEntry 83{ 84 self = [super init]; 85 86 if (self) 87 { 88 ASSIGN(_identifier, theID); 89 ASSIGN(_domain, theDomain); 90 ASSIGN(_source, theSource); 91 ASSIGN(_entry, theEntry); 92 _members = nil; 93 } 94 95 return self; 96} 97 98- (void) dealloc 99{ 100 RELEASE(_identifier); 101 RELEASE(_domain); 102 RELEASE(_source); 103 RELEASE(_entry); 104 RELEASE(_members); 105 106 [super dealloc]; 107} 108 109+ (id) groupWithIdentifier: (NSString *) theID 110 inDomain: (NSString *) domain 111{ 112 NSRange r; 113 NSString *uid, *inDomain; 114 SOGoSystemDefaults *sd; 115 116 uid = [theID hasPrefix: @"@"] ? [theID substringFromIndex: 1] : theID; 117 inDomain = domain; 118 119 sd = [SOGoSystemDefaults sharedSystemDefaults]; 120 if ([sd enableDomainBasedUID]) 121 { 122 /* Split domain from uid */ 123 r = [uid rangeOfString: @"@" options: NSBackwardsSearch]; 124 if (r.location != NSNotFound) 125 { 126 if (!domain) 127 inDomain = [uid substringFromIndex: r.location + 1]; 128 uid = [uid substringToIndex: r.location]; 129 } 130 } 131 132 return [SOGoGroup groupWithValue: uid 133 andSourceSelector: @selector (lookupGroupEntryByUID:inDomain:) 134 inDomain: inDomain]; 135} 136 137+ (id) groupWithEmail: (NSString *) theEmail 138 inDomain: (NSString *) domain 139{ 140 return [SOGoGroup groupWithValue: theEmail 141 andSourceSelector: @selector (lookupGroupEntryByEmail:inDomain:) 142 inDomain: domain]; 143} 144 145// 146// Returns nil if theValue doesn't match to a group 147// (so its objectClass isn't a group) 148// 149+ (id) groupWithValue: (NSString *) theValue 150 andSourceSelector: (SEL) theSelector 151 inDomain: (NSString *) domain 152{ 153 NSArray *allSources; 154 NGLdapEntry *entry; 155 NSObject <SOGoSource, SOGoDNSource> *source; 156 id o; 157 NSEnumerator *gclasses; 158 NSString *gclass; 159 160 int i; 161 162 // Don't bother looking in all sources if the 163 // supplied value is nil. 164 if (!theValue) 165 return nil; 166 167 allSources = [[SOGoUserManager sharedUserManager] 168 sourceIDsInDomain: domain]; 169 entry = nil; 170 o = nil; 171 172 for (i = 0; i < [allSources count]; i++) 173 { 174 source = (NSObject <SOGoSource, SOGoDNSource> *) [[SOGoUserManager sharedUserManager] sourceWithID: [allSources objectAtIndex: i]]; 175 176 // Our different sources might not all implements groups support 177 if ([source respondsToSelector: theSelector]) 178 entry = [source performSelector: theSelector 179 withObject: theValue 180 withObject: domain]; 181 if (entry) 182 break; 183 184 entry = nil; 185 } 186 187 if (entry) 188 { 189 NSArray *classes; 190 191 // We check to see if it's a group 192 classes = [[entry asDictionary] objectForKey: @"objectclass"]; 193 194 if (classes) 195 { 196 /* LDAP records returned as dictionaries may contain NSString or 197 NSArray values, depending on whether the amount of values 198 assigned to a key is 1 or more. Since this can occur with 199 "objectclass" too, we need to check whether "classes" is actually 200 an NSString instance... */ 201 if ([classes isKindOfClass: [NSString class]]) 202 classes = [NSArray arrayWithObject: 203 [(NSString *) classes lowercaseString]]; 204 else 205 { 206 int i, c; 207 208 classes = [NSMutableArray arrayWithArray: classes]; 209 c = [classes count]; 210 for (i = 0; i < c; i++) 211 [(id)classes replaceObjectAtIndex: i 212 withObject: [[classes objectAtIndex: i] lowercaseString]]; 213 } 214 } 215 216 gclasses = [[source groupObjectClasses] objectEnumerator]; 217 while (gclass = [gclasses nextObject]) 218 if ([classes containsObject: gclass]) 219 { 220 // Found a group, let's return it. 221 o = [[self alloc] initWithIdentifier: theValue 222 domain: domain 223 source: source 224 entry: entry]; 225 AUTORELEASE(o); 226 } 227 } 228 229 return o; 230} 231 232// 233// This method actually try to obtain all members 234// from either dynamic of static groups. 235// 236- (NSArray *) members 237{ 238 NSMutableArray *dns, *uids, *logins; 239 NSString *dn, *login; 240 SOGoUserManager *um; 241 NSDictionary *d; 242 SOGoUser *user; 243 NSArray *o; 244 NSAutoreleasePool *pool; 245 int i, c; 246 247 if (!_members) 248 { 249 _members = [NSMutableArray new]; 250 uids = [NSMutableArray array]; 251 dns = [NSMutableArray array]; 252 logins = [NSMutableArray array]; 253 254 // We check if it's a static group 255 // Fetch "members" - we get DNs 256 d = [_entry asDictionary]; 257 o = [d objectForKey: @"member"]; 258 CHECK_CLASS(o); 259 if (o) [dns addObjectsFromArray: o]; 260 261 // Fetch "uniqueMembers" - we get DNs 262 o = [d objectForKey: @"uniquemember"]; 263 CHECK_CLASS(o); 264 if (o) [dns addObjectsFromArray: o]; 265 266 // Fetch "memberUid" - we get UID (like login names) 267 o = [d objectForKey: @"memberuid"]; 268 CHECK_CLASS(o); 269 if (o) [uids addObjectsFromArray: o]; 270 271 c = [dns count] + [uids count]; 272 273 // We deal with a static group, let's add the members 274 if (c) 275 { 276 um = [SOGoUserManager sharedUserManager]; 277 278 // We add members for whom we have their associated DN 279 for (i = 0; i < [dns count]; i++) 280 { 281 pool = [NSAutoreleasePool new]; 282 dn = [dns objectAtIndex: i]; 283 login = [um getLoginForDN: [dn lowercaseString]]; 284 user = [SOGoUser userWithLogin: login roles: nil]; 285 if (user) 286 { 287 [logins addObject: login]; 288 [_members addObject: user]; 289 } 290 [pool release]; 291 } 292 293 // We add members for whom we have their associated login name 294 for (i = 0; i < [uids count]; i++) 295 { 296 pool = [NSAutoreleasePool new]; 297 login = [uids objectAtIndex: i]; 298 user = [SOGoUser userWithLogin: login roles: nil]; 299 300 if (user) 301 { 302 [logins addObject: login]; 303 [_members addObject: user]; 304 } 305 [pool release]; 306 } 307 308 309 // We are done fetching members, let's cache the members of the group 310 // (ie., their UIDs) in memcached to speed up -hasMemberWithUID. 311 [[SOGoCache sharedCache] setValue: [logins componentsJoinedByString: @","] 312 forKey: [NSString stringWithFormat: @"%@+%@", _identifier, _domain]]; 313 } 314 else 315 { 316 // We deal with a dynamic group, let's search all users for whom 317 // memberOf is equal to our group's DN. 318 // We also need to look for labelelURI? 319 } 320 } 321 322 return _members; 323} 324 325// 326// 327// 328- (BOOL) hasMemberWithUID: (NSString *) memberUID 329{ 330 331 BOOL rc; 332 333 rc = NO; 334 335 // If _members is initialized, we use it as it's very accurate. 336 // Otherwise, we fallback on memcached in order to avoid 337 // decomposing the group all the time just to see if a user 338 // is a member of it. 339 if (_members) 340 { 341 NSString *currentUID; 342 343 int count, max; 344 max = [_members count]; 345 for (count = 0; !rc && count < max; count++) 346 { 347 currentUID = [[_members objectAtIndex: count] login]; 348 rc = [memberUID isEqualToString: currentUID]; 349 } 350 351 } 352 else 353 { 354 NSString *key, *value;; 355 NSArray *a; 356 357 key = [NSString stringWithFormat: @"%@+%@", _identifier, _domain]; 358 value = [[SOGoCache sharedCache] valueForKey: key]; 359 360 361 // If the value isn't in memcached, that probably means -members was never called. 362 // We call it only once here. 363 if (!value) 364 { 365 [self members]; 366 value = [[SOGoCache sharedCache] valueForKey: key]; 367 } 368 369 a = [value componentsSeparatedByString: @","]; 370 rc = [a containsObject: memberUID]; 371 } 372 373 return rc; 374} 375 376@end 377