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 }