001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020 package org.apache.directory.server.core.authz; 021 022 023 import java.util.HashMap; 024 import java.util.HashSet; 025 import java.util.List; 026 import java.util.Map; 027 import java.util.Set; 028 029 import javax.naming.directory.SearchControls; 030 031 import org.apache.directory.server.constants.ServerDNConstants; 032 import org.apache.directory.server.core.CoreSession; 033 import org.apache.directory.server.core.filtering.EntryFilteringCursor; 034 import org.apache.directory.server.core.interceptor.context.SearchOperationContext; 035 import org.apache.directory.server.core.partition.PartitionNexus; 036 import org.apache.directory.server.i18n.I18n; 037 import org.apache.directory.shared.ldap.constants.SchemaConstants; 038 import org.apache.directory.shared.ldap.entry.StringValue; 039 import org.apache.directory.shared.ldap.entry.EntryAttribute; 040 import org.apache.directory.shared.ldap.entry.Modification; 041 import org.apache.directory.shared.ldap.entry.ModificationOperation; 042 import org.apache.directory.shared.ldap.entry.ServerEntry; 043 import org.apache.directory.shared.ldap.entry.Value; 044 import org.apache.directory.shared.ldap.exception.LdapException; 045 import org.apache.directory.shared.ldap.filter.BranchNode; 046 import org.apache.directory.shared.ldap.filter.EqualityNode; 047 import org.apache.directory.shared.ldap.filter.OrNode; 048 import org.apache.directory.shared.ldap.message.AliasDerefMode; 049 import org.apache.directory.shared.ldap.name.DN; 050 import org.apache.directory.shared.ldap.schema.AttributeType; 051 import org.apache.directory.shared.ldap.schema.SchemaManager; 052 import org.apache.directory.shared.ldap.schema.normalizers.OidNormalizer; 053 import org.slf4j.Logger; 054 import org.slf4j.LoggerFactory; 055 056 057 /** 058 * A cache for tracking static group membership. 059 * 060 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 061 * @version $Rev: 928945 $ 062 */ 063 public class GroupCache 064 { 065 /** the logger for this class */ 066 private static final Logger LOG = LoggerFactory.getLogger( GroupCache.class ); 067 068 /** Speedup for logs */ 069 private static final boolean IS_DEBUG = LOG.isDebugEnabled(); 070 071 /** String key for the DN of a group to a Set (HashSet) for the Strings of member DNs */ 072 private final Map<String, Set<String>> groups = new HashMap<String, Set<String>>(); 073 074 /** a handle on the partition nexus */ 075 private final PartitionNexus nexus; 076 077 /** A storage for the member attributeType */ 078 private AttributeType memberAT; 079 080 /** A storage for the uniqueMember attributeType */ 081 private AttributeType uniqueMemberAT; 082 083 /** 084 * The OIDs normalizer map 085 */ 086 private Map<String, OidNormalizer> normalizerMap; 087 088 /** the normalized dn of the administrators group */ 089 private DN administratorsGroupDn; 090 091 private static final Set<DN> EMPTY_GROUPS = new HashSet<DN>(); 092 093 094 /** 095 * Creates a static group cache. 096 * 097 * @param directoryService the directory service core 098 * @throws LdapException if there are failures on initialization 099 */ 100 public GroupCache( CoreSession session ) throws Exception 101 { 102 SchemaManager schemaManager = session.getDirectoryService().getSchemaManager(); 103 normalizerMap = schemaManager.getNormalizerMapping(); 104 nexus = session.getDirectoryService().getPartitionNexus(); 105 memberAT = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.MEMBER_AT_OID ); 106 uniqueMemberAT = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.UNIQUE_MEMBER_AT_OID ); 107 108 // stuff for dealing with the admin group 109 administratorsGroupDn = parseNormalized( ServerDNConstants.ADMINISTRATORS_GROUP_DN ); 110 111 initialize( session ); 112 } 113 114 115 private DN parseNormalized( String name ) throws LdapException 116 { 117 DN dn = new DN( name ); 118 dn.normalize( normalizerMap ); 119 return dn; 120 } 121 122 123 private void initialize( CoreSession session ) throws Exception 124 { 125 // search all naming contexts for static groups and generate 126 // normalized sets of members to cache within the map 127 128 BranchNode filter = new OrNode(); 129 filter.addNode( new EqualityNode<String>( SchemaConstants.OBJECT_CLASS_AT, new StringValue( 130 SchemaConstants.GROUP_OF_NAMES_OC ) ) ); 131 filter.addNode( new EqualityNode<String>( SchemaConstants.OBJECT_CLASS_AT, new StringValue( 132 SchemaConstants.GROUP_OF_UNIQUE_NAMES_OC ) ) ); 133 134 Set<String> suffixes = nexus.listSuffixes( null ); 135 136 for ( String suffix:suffixes ) 137 { 138 DN baseDn = new DN( suffix ).normalize( normalizerMap ); 139 SearchControls ctls = new SearchControls(); 140 ctls.setSearchScope( SearchControls.SUBTREE_SCOPE ); 141 142 SearchOperationContext searchOperationContext = new SearchOperationContext( session, 143 baseDn, filter, ctls ); 144 searchOperationContext.setAliasDerefMode( AliasDerefMode.DEREF_ALWAYS ); 145 EntryFilteringCursor results = nexus.search( searchOperationContext ); 146 147 while ( results.next() ) 148 { 149 ServerEntry result = results.get(); 150 DN groupDn = result.getDn().normalize( normalizerMap ); 151 EntryAttribute members = getMemberAttribute( result ); 152 153 if ( members != null ) 154 { 155 Set<String> memberSet = new HashSet<String>( members.size() ); 156 addMembers( memberSet, members ); 157 groups.put( groupDn.getNormName(), memberSet ); 158 } 159 else 160 { 161 LOG.warn( "Found group '{}' without any member or uniqueMember attributes", groupDn.getName() ); 162 } 163 } 164 165 results.close(); 166 } 167 168 if ( IS_DEBUG ) 169 { 170 LOG.debug( "group cache contents on startup:\n {}", groups ); 171 } 172 } 173 174 175 /** 176 * Gets the member attribute regardless of whether groupOfNames or 177 * groupOfUniqueNames is used. 178 * 179 * @param entry the entry inspected for member attributes 180 * @return the member attribute 181 */ 182 private EntryAttribute getMemberAttribute( ServerEntry entry ) throws LdapException 183 { 184 EntryAttribute oc = entry.get( SchemaConstants.OBJECT_CLASS_AT ); 185 186 if ( oc == null ) 187 { 188 EntryAttribute member = entry.get( memberAT ); 189 190 if ( member != null ) 191 { 192 return member; 193 } 194 195 EntryAttribute uniqueMember = entry.get( uniqueMemberAT ); 196 197 if ( uniqueMember != null ) 198 { 199 return uniqueMember; 200 } 201 202 return null; 203 } 204 205 if ( oc.contains( SchemaConstants.GROUP_OF_NAMES_OC ) || oc.contains( SchemaConstants.GROUP_OF_NAMES_OC_OID ) ) 206 { 207 return entry.get( memberAT ); 208 } 209 210 if ( oc.contains( SchemaConstants.GROUP_OF_UNIQUE_NAMES_OC ) 211 || oc.contains( SchemaConstants.GROUP_OF_UNIQUE_NAMES_OC_OID ) ) 212 { 213 return entry.get( uniqueMemberAT ); 214 } 215 216 return null; 217 } 218 219 220 /** 221 * Adds normalized member DNs to the set of normalized member names. 222 * 223 * @param memberSet the set of member Dns (Strings) 224 * @param members the member attribute values being added 225 * @throws LdapException if there are problems accessing the attr values 226 */ 227 private void addMembers( Set<String> memberSet, EntryAttribute members ) throws LdapException 228 { 229 for ( Value<?> value : members ) 230 { 231 232 // get and normalize the DN of the member 233 String memberDn = value.getString(); 234 235 try 236 { 237 memberDn = parseNormalized( memberDn ).getNormName(); 238 } 239 catch ( LdapException e ) 240 { 241 LOG.warn( "Malformed member DN in groupOf[Unique]Names entry. Member not added to GroupCache.", e ); 242 } 243 244 memberSet.add( memberDn ); 245 } 246 } 247 248 249 /** 250 * Removes a set of member names from an existing set. 251 * 252 * @param memberSet the set of normalized member DNs 253 * @param members the set of member values 254 * @throws LdapException if there are problems accessing the attr values 255 */ 256 private void removeMembers( Set<String> memberSet, EntryAttribute members ) throws LdapException 257 { 258 for ( Value<?> value : members ) 259 { 260 // get and normalize the DN of the member 261 String memberDn = value.getString(); 262 263 try 264 { 265 memberDn = parseNormalized( memberDn ).getNormName(); 266 } 267 catch ( LdapException e ) 268 { 269 LOG.warn( "Malformed member DN in groupOf[Unique]Names entry. Member not removed from GroupCache.", e ); 270 } 271 272 memberSet.remove( memberDn ); 273 } 274 } 275 276 277 /** 278 * Adds a groups members to the cache. Called by interceptor to account for new 279 * group additions. 280 * 281 * @param name the user provided name for the group entry 282 * @param entry the group entry's attributes 283 * @throws LdapException if there are problems accessing the attr values 284 */ 285 public void groupAdded( DN name, ServerEntry entry ) throws LdapException 286 { 287 EntryAttribute members = getMemberAttribute( entry ); 288 289 if ( members == null ) 290 { 291 return; 292 } 293 294 Set<String> memberSet = new HashSet<String>( members.size() ); 295 addMembers( memberSet, members ); 296 groups.put( name.getNormName(), memberSet ); 297 298 if ( IS_DEBUG ) 299 { 300 LOG.debug( "group cache contents after adding '{}' :\n {}", name.getName(), groups ); 301 } 302 } 303 304 305 /** 306 * Deletes a group's members from the cache. Called by interceptor to account for 307 * the deletion of groups. 308 * 309 * @param name the normalized DN of the group entry 310 * @param entry the attributes of entry being deleted 311 */ 312 public void groupDeleted( DN name, ServerEntry entry ) throws LdapException 313 { 314 EntryAttribute members = getMemberAttribute( entry ); 315 316 if ( members == null ) 317 { 318 return; 319 } 320 321 groups.remove( name.getNormName() ); 322 323 if ( IS_DEBUG ) 324 { 325 LOG.debug( "group cache contents after deleting '{}' :\n {}", name.getName(), groups ); 326 } 327 } 328 329 330 /** 331 * Utility method to modify a set of member names based on a modify operation 332 * that changes the members of a group. 333 * 334 * @param memberSet the set of members to be altered 335 * @param modOp the type of modify operation being performed 336 * @param members the members being added, removed or replaced 337 * @throws LdapException if there are problems accessing attribute values 338 */ 339 private void modify( Set<String> memberSet, ModificationOperation modOp, EntryAttribute members ) 340 throws LdapException 341 { 342 343 switch ( modOp ) 344 { 345 case ADD_ATTRIBUTE: 346 addMembers( memberSet, members ); 347 break; 348 349 case REPLACE_ATTRIBUTE: 350 if ( members.size() > 0 ) 351 { 352 memberSet.clear(); 353 addMembers( memberSet, members ); 354 } 355 356 break; 357 358 case REMOVE_ATTRIBUTE: 359 removeMembers( memberSet, members ); 360 break; 361 362 default: 363 throw new InternalError( I18n.err( I18n.ERR_235, modOp ) ); 364 } 365 } 366 367 368 /** 369 * Modifies the cache to reflect changes via modify operations to the group entries. 370 * Called by the interceptor to account for modify ops on groups. 371 * 372 * @param name the normalized name of the group entry modified 373 * @param mods the modification operations being performed 374 * @param entry the group entry being modified 375 * @throws LdapException if there are problems accessing attribute values 376 */ 377 public void groupModified( DN name, List<Modification> mods, ServerEntry entry, SchemaManager schemaManager ) 378 throws LdapException 379 { 380 EntryAttribute members = null; 381 String memberAttrId = null; 382 EntryAttribute oc = entry.get( SchemaConstants.OBJECT_CLASS_AT ); 383 384 if ( oc.contains( SchemaConstants.GROUP_OF_NAMES_OC ) ) 385 { 386 members = entry.get( memberAT ); 387 memberAttrId = SchemaConstants.MEMBER_AT; 388 } 389 390 if ( oc.contains( SchemaConstants.GROUP_OF_UNIQUE_NAMES_OC ) ) 391 { 392 members = entry.get( uniqueMemberAT ); 393 memberAttrId = SchemaConstants.UNIQUE_MEMBER_AT; 394 } 395 396 if ( members == null ) 397 { 398 return; 399 } 400 401 for ( Modification modification : mods ) 402 { 403 if ( memberAttrId.equalsIgnoreCase( modification.getAttribute().getId() ) ) 404 { 405 Set<String> memberSet = groups.get( name.getNormName() ); 406 407 if ( memberSet != null ) 408 { 409 modify( memberSet, modification.getOperation(), modification.getAttribute() ); 410 } 411 412 break; 413 } 414 } 415 416 if ( IS_DEBUG ) 417 { 418 LOG.debug( "group cache contents after modifying '{}' :\n {}", name.getName(), groups ); 419 } 420 } 421 422 423 /** 424 * Modifies the cache to reflect changes via modify operations to the group entries. 425 * Called by the interceptor to account for modify ops on groups. 426 * 427 * @param name the normalized name of the group entry modified 428 * @param modOp the modify operation being performed 429 * @param mods the modifications being performed 430 * @throws LdapException if there are problems accessing attribute values 431 */ 432 public void groupModified( DN name, ModificationOperation modOp, ServerEntry mods ) throws LdapException 433 { 434 EntryAttribute members = getMemberAttribute( mods ); 435 436 if ( members == null ) 437 { 438 return; 439 } 440 441 Set<String> memberSet = groups.get( name.getNormName() ); 442 443 if ( memberSet != null ) 444 { 445 modify( memberSet, modOp, members ); 446 } 447 448 if ( IS_DEBUG ) 449 { 450 LOG.debug( "group cache contents after modifying '{}' :\n {}", name.getName(), groups ); 451 } 452 } 453 454 455 /** 456 * An optimization. By having this method here we can directly access the group 457 * membership information and lookup to see if the principalDn is contained within. 458 * 459 * @param principalDn the normalized DN of the user to check if they are an admin 460 * @return true if the principal is an admin or the admin 461 */ 462 public final boolean isPrincipalAnAdministrator( DN principalDn ) 463 { 464 if ( principalDn.getNormName().equals( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED ) ) 465 { 466 return true; 467 } 468 469 Set<String> members = groups.get( administratorsGroupDn.getNormName() ); 470 471 if ( members == null ) 472 { 473 LOG.warn( "What do you mean there is no administrators group? This is bad news." ); 474 return false; 475 } 476 477 return members.contains( principalDn.getNormName() ); 478 } 479 480 481 /** 482 * Gets the set of groups a user is a member of. The groups are returned 483 * as normalized Name objects within the set. 484 * 485 * @param member the member (user) to get the groups for 486 * @return a Set of Name objects representing the groups 487 * @throws LdapException if there are problems accessing attribute values 488 */ 489 public Set<DN> getGroups( String member ) throws LdapException 490 { 491 DN normMember; 492 493 try 494 { 495 normMember = parseNormalized( member ); 496 } 497 catch ( LdapException e ) 498 { 499 LOG 500 .warn( 501 "Malformed member DN. Could not find groups for member '{}' in GroupCache. Returning empty set for groups!", 502 member, e ); 503 return EMPTY_GROUPS; 504 } 505 506 Set<DN> memberGroups = null; 507 508 for ( String group : groups.keySet() ) 509 { 510 Set<String> members = groups.get( group ); 511 512 if ( members == null ) 513 { 514 continue; 515 } 516 517 if ( members.contains( normMember.getNormName() ) ) 518 { 519 if ( memberGroups == null ) 520 { 521 memberGroups = new HashSet<DN>(); 522 } 523 524 memberGroups.add( parseNormalized( group ) ); 525 } 526 } 527 528 if ( memberGroups == null ) 529 { 530 return EMPTY_GROUPS; 531 } 532 533 return memberGroups; 534 } 535 536 537 public boolean groupRenamed( DN oldName, DN newName ) 538 { 539 Set<String> members = groups.remove( oldName.getNormName() ); 540 541 if ( members != null ) 542 { 543 groups.put( newName.getNormName(), members ); 544 545 if ( IS_DEBUG ) 546 { 547 LOG.debug( "group cache contents after renaming '{}' :\n{}", oldName.getName(), groups ); 548 } 549 550 return true; 551 } 552 553 return false; 554 } 555 }