View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.server.core.authz;
21  
22  
23  import javax.naming.directory.SearchControls;
24  import javax.naming.NamingException;
25  
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  
33  import org.apache.directory.server.constants.ServerDNConstants;
34  import org.apache.directory.server.core.CoreSession;
35  import org.apache.directory.server.core.entry.ServerAttribute;
36  import org.apache.directory.server.core.entry.ServerEntry;
37  import org.apache.directory.server.core.filtering.EntryFilteringCursor;
38  import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
39  import org.apache.directory.server.core.partition.PartitionNexus;
40  import org.apache.directory.server.schema.registries.AttributeTypeRegistry;
41  import org.apache.directory.server.schema.registries.Registries;
42  import org.apache.directory.shared.ldap.constants.SchemaConstants;
43  import org.apache.directory.shared.ldap.entry.EntryAttribute;
44  import org.apache.directory.shared.ldap.entry.Modification;
45  import org.apache.directory.shared.ldap.entry.ModificationOperation;
46  import org.apache.directory.shared.ldap.entry.Value;
47  import org.apache.directory.shared.ldap.entry.client.ClientStringValue;
48  import org.apache.directory.shared.ldap.filter.BranchNode;
49  import org.apache.directory.shared.ldap.filter.EqualityNode;
50  import org.apache.directory.shared.ldap.filter.OrNode;
51  import org.apache.directory.shared.ldap.message.AliasDerefMode;
52  import org.apache.directory.shared.ldap.name.LdapDN;
53  import org.apache.directory.shared.ldap.schema.AttributeType;
54  import org.apache.directory.shared.ldap.schema.OidNormalizer;
55  import org.slf4j.Logger;
56  import org.slf4j.LoggerFactory;
57  
58  
59  /**
60   * A cache for tracking static group membership.
61   *
62   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
63   * @version $Rev: 690042 $
64   */
65  public class GroupCache
66  {
67      /** the logger for this class */
68      private static final Logger LOG = LoggerFactory.getLogger( GroupCache.class );
69  
70      /** Speedup for logs */
71      private static final boolean IS_DEBUG = LOG.isDebugEnabled();
72  
73      /** String key for the DN of a group to a Set (HashSet) for the Strings of member DNs */
74      private final Map<String, Set<String>> groups = new HashMap<String, Set<String>>();
75  
76      /** a handle on the partition nexus */
77      private final PartitionNexus nexus;
78  
79      /** A storage for the member attributeType */
80      private AttributeType memberAT;
81  
82      /** A storage for the uniqueMember attributeType */
83      private AttributeType uniqueMemberAT;
84  
85      /**
86       * The OIDs normalizer map
87       */
88      private Map<String, OidNormalizer> normalizerMap;
89  
90      /** the normalized dn of the administrators group */
91      private LdapDN administratorsGroupDn;
92  
93      private static final Set<LdapDN> EMPTY_GROUPS = new HashSet<LdapDN>();
94  
95  
96      /**
97       * Creates a static group cache.
98       *
99       * @param directoryService the directory service core
100      * @throws NamingException if there are failures on initialization 
101      */
102     public GroupCache( CoreSession session ) throws Exception
103     {
104         normalizerMap = session.getDirectoryService().getRegistries().getAttributeTypeRegistry().getNormalizerMapping();
105         nexus = session.getDirectoryService().getPartitionNexus();
106         AttributeTypeRegistry attributeTypeRegistry = session.getDirectoryService()
107             .getRegistries().getAttributeTypeRegistry();
108 
109         memberAT = attributeTypeRegistry.lookup( SchemaConstants.MEMBER_AT_OID );
110         uniqueMemberAT = attributeTypeRegistry.lookup( SchemaConstants.UNIQUE_MEMBER_AT_OID );
111 
112         // stuff for dealing with the admin group
113         administratorsGroupDn = parseNormalized( ServerDNConstants.ADMINISTRATORS_GROUP_DN );
114 
115         initialize( session );
116     }
117 
118 
119     private LdapDN parseNormalized( String name ) throws NamingException
120     {
121         LdapDN dn = new LdapDN( name );
122         dn.normalize( normalizerMap );
123         return dn;
124     }
125 
126 
127     private void initialize( CoreSession session ) throws Exception
128     {
129         // search all naming contexts for static groups and generate
130         // normalized sets of members to cache within the map
131 
132         BranchNode filter = new OrNode();
133         filter.addNode( new EqualityNode<String>( SchemaConstants.OBJECT_CLASS_AT, new ClientStringValue(
134             SchemaConstants.GROUP_OF_NAMES_OC ) ) );
135         filter.addNode( new EqualityNode<String>( SchemaConstants.OBJECT_CLASS_AT, new ClientStringValue(
136             SchemaConstants.GROUP_OF_UNIQUE_NAMES_OC ) ) );
137 
138         Iterator<String> suffixes = nexus.listSuffixes( null );
139 
140         while ( suffixes.hasNext() )
141         {
142             String suffix = suffixes.next();
143             LdapDN baseDn = new LdapDN( suffix );
144             SearchControls ctls = new SearchControls();
145             ctls.setSearchScope( SearchControls.SUBTREE_SCOPE );
146             
147             
148             EntryFilteringCursor results = nexus.search( new SearchOperationContext( session,
149                 baseDn, AliasDerefMode.DEREF_ALWAYS, filter, ctls ) );
150 
151             while ( results.next() )
152             {
153                 ServerEntry result = results.get();
154                 LdapDN groupDn = result.getDn().normalize( normalizerMap );
155                 EntryAttribute members = getMemberAttribute( result );
156 
157                 if ( members != null )
158                 {
159                     Set<String> memberSet = new HashSet<String>( members.size() );
160                     addMembers( memberSet, members );
161                     groups.put( groupDn.getNormName(), memberSet );
162                 }
163                 else
164                 {
165                     LOG.warn( "Found group '{}' without any member or uniqueMember attributes", groupDn.getUpName() );
166                 }
167             }
168 
169             results.close();
170         }
171 
172         if ( IS_DEBUG )
173         {
174             LOG.debug( "group cache contents on startup:\n {}", groups );
175         }
176     }
177 
178 
179     /**
180      * Gets the member attribute regardless of whether groupOfNames or
181      * groupOfUniqueNames is used.
182      *
183      * @param entry the entry inspected for member attributes
184      * @return the member attribute
185      */
186     private EntryAttribute getMemberAttribute( ServerEntry entry ) throws NamingException
187     {
188         EntryAttribute oc = entry.get( SchemaConstants.OBJECT_CLASS_AT );
189 
190         if ( oc == null )
191         {
192             EntryAttribute member = entry.get( memberAT );
193 
194             if ( member != null )
195             {
196                 return member;
197             }
198 
199             EntryAttribute uniqueMember = entry.get( uniqueMemberAT );
200 
201             if ( uniqueMember != null )
202             {
203                 return uniqueMember;
204             }
205 
206             return null;
207         }
208 
209         if ( oc.contains( SchemaConstants.GROUP_OF_NAMES_OC ) || oc.contains( SchemaConstants.GROUP_OF_NAMES_OC_OID ) )
210         {
211             return entry.get( memberAT );
212         }
213 
214         if ( oc.contains( SchemaConstants.GROUP_OF_UNIQUE_NAMES_OC )
215             || oc.contains( SchemaConstants.GROUP_OF_UNIQUE_NAMES_OC_OID ) )
216         {
217             return entry.get( uniqueMemberAT );
218         }
219 
220         return null;
221     }
222 
223 
224     /**
225      * Adds normalized member DNs to the set of normalized member names.
226      *
227      * @param memberSet the set of member Dns (Strings)
228      * @param members the member attribute values being added
229      * @throws NamingException if there are problems accessing the attr values
230      */
231     private void addMembers( Set<String> memberSet, EntryAttribute members ) throws NamingException
232     {
233         for ( Value<?> value : members )
234         {
235 
236             // get and normalize the DN of the member
237             String memberDn = ( String ) value.get();
238 
239             try
240             {
241                 memberDn = parseNormalized( memberDn ).toString();
242             }
243             catch ( NamingException e )
244             {
245                 LOG.warn( "Malformed member DN in groupOf[Unique]Names entry.  Member not added to GroupCache.", e );
246             }
247 
248             memberSet.add( memberDn );
249         }
250     }
251 
252 
253     /**
254      * Removes a set of member names from an existing set.
255      *
256      * @param memberSet the set of normalized member DNs
257      * @param members the set of member values
258      * @throws NamingException if there are problems accessing the attr values
259      */
260     private void removeMembers( Set<String> memberSet, EntryAttribute members ) throws NamingException
261     {
262         for ( Value<?> value : members )
263         {
264             // get and normalize the DN of the member
265             String memberDn = ( String ) value.get();
266 
267             try
268             {
269                 memberDn = parseNormalized( memberDn ).toString();
270             }
271             catch ( NamingException e )
272             {
273                 LOG.warn( "Malformed member DN in groupOf[Unique]Names entry.  Member not removed from GroupCache.", e );
274             }
275 
276             memberSet.remove( memberDn );
277         }
278     }
279 
280 
281     /**
282      * Adds a groups members to the cache.  Called by interceptor to account for new
283      * group additions.
284      *
285      * @param name the user provided name for the group entry
286      * @param entry the group entry's attributes
287      * @throws NamingException if there are problems accessing the attr values
288      */
289     public void groupAdded( LdapDN name, ServerEntry entry ) throws NamingException
290     {
291         EntryAttribute members = getMemberAttribute( entry );
292 
293         if ( members == null )
294         {
295             return;
296         }
297 
298         Set<String> memberSet = new HashSet<String>( members.size() );
299         addMembers( memberSet, members );
300         groups.put( name.getNormName(), memberSet );
301 
302         if ( IS_DEBUG )
303         {
304             LOG.debug( "group cache contents after adding '{}' :\n {}", name.getUpName(), groups );
305         }
306     }
307 
308 
309     /**
310      * Deletes a group's members from the cache.  Called by interceptor to account for
311      * the deletion of groups.
312      *
313      * @param name the normalized DN of the group entry
314      * @param entry the attributes of entry being deleted
315      */
316     public void groupDeleted( LdapDN name, ServerEntry entry ) throws NamingException
317     {
318         EntryAttribute members = getMemberAttribute( entry );
319 
320         if ( members == null )
321         {
322             return;
323         }
324 
325         groups.remove( name.getNormName() );
326 
327         if ( IS_DEBUG )
328         {
329             LOG.debug( "group cache contents after deleting '{}' :\n {}", name.getUpName(), groups );
330         }
331     }
332 
333 
334     /**
335      * Utility method to modify a set of member names based on a modify operation
336      * that changes the members of a group.
337      *
338      * @param memberSet the set of members to be altered
339      * @param modOp the type of modify operation being performed
340      * @param members the members being added, removed or replaced
341      * @throws NamingException if there are problems accessing attribute values
342      */
343     private void modify( Set<String> memberSet, ModificationOperation modOp, EntryAttribute members )
344         throws NamingException
345     {
346 
347         switch ( modOp )
348         {
349             case ADD_ATTRIBUTE:
350                 addMembers( memberSet, members );
351                 break;
352 
353             case REPLACE_ATTRIBUTE:
354                 if ( members.size() > 0 )
355                 {
356                     memberSet.clear();
357                     addMembers( memberSet, members );
358                 }
359 
360                 break;
361 
362             case REMOVE_ATTRIBUTE:
363                 removeMembers( memberSet, members );
364                 break;
365 
366             default:
367                 throw new InternalError( "Undefined modify operation value of " + modOp );
368         }
369     }
370 
371 
372     /**
373      * Modifies the cache to reflect changes via modify operations to the group entries.
374      * Called by the interceptor to account for modify ops on groups.
375      *
376      * @param name the normalized name of the group entry modified
377      * @param mods the modification operations being performed
378      * @param entry the group entry being modified
379      * @throws NamingException if there are problems accessing attribute  values
380      */
381     public void groupModified( LdapDN name, List<Modification> mods, ServerEntry entry, Registries registries )
382         throws NamingException
383     {
384         EntryAttribute members = null;
385         String memberAttrId = null;
386         EntryAttribute oc = entry.get( SchemaConstants.OBJECT_CLASS_AT );
387 
388         if ( oc.contains( SchemaConstants.GROUP_OF_NAMES_OC ) )
389         {
390             members = entry.get( memberAT );
391             memberAttrId = SchemaConstants.MEMBER_AT;
392         }
393 
394         if ( oc.contains( SchemaConstants.GROUP_OF_UNIQUE_NAMES_OC ) )
395         {
396             members = entry.get( uniqueMemberAT );
397             memberAttrId = SchemaConstants.UNIQUE_MEMBER_AT;
398         }
399 
400         if ( members == null )
401         {
402             return;
403         }
404 
405         for ( Modification modification : mods )
406         {
407             if ( memberAttrId.equalsIgnoreCase( modification.getAttribute().getId() ) )
408             {
409                 Set<String> memberSet = groups.get( name.getNormName() );
410 
411                 if ( memberSet != null )
412                 {
413                     modify( memberSet, modification.getOperation(), ( ServerAttribute ) modification.getAttribute() );
414                 }
415 
416                 break;
417             }
418         }
419 
420         if ( IS_DEBUG )
421         {
422             LOG.debug( "group cache contents after modifying '{}' :\n {}", name.getUpName(), groups );
423         }
424     }
425 
426 
427     /**
428      * Modifies the cache to reflect changes via modify operations to the group entries.
429      * Called by the interceptor to account for modify ops on groups.
430      *
431      * @param name the normalized name of the group entry modified
432      * @param modOp the modify operation being performed
433      * @param mods the modifications being performed
434      * @throws NamingException if there are problems accessing attribute  values
435      */
436     public void groupModified( LdapDN name, ModificationOperation modOp, ServerEntry mods ) throws NamingException
437     {
438         EntryAttribute members = getMemberAttribute( mods );
439 
440         if ( members == null )
441         {
442             return;
443         }
444 
445         Set<String> memberSet = groups.get( name.getNormName() );
446 
447         if ( memberSet != null )
448         {
449             modify( memberSet, modOp, members );
450         }
451 
452         if ( IS_DEBUG )
453         {
454             LOG.debug( "group cache contents after modifying '{}' :\n {}", name.getUpName(), groups );
455         }
456     }
457 
458 
459     /**
460      * An optimization.  By having this method here we can directly access the group
461      * membership information and lookup to see if the principalDn is contained within.
462      * 
463      * @param principalDn the normalized DN of the user to check if they are an admin
464      * @return true if the principal is an admin or the admin
465      */
466     public final boolean isPrincipalAnAdministrator( LdapDN principalDn )
467     {
468         if ( principalDn.getNormName().equals( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED ) )
469         {
470             return true;
471         }
472 
473         Set<String> members = groups.get( administratorsGroupDn.getNormName() );
474 
475         if ( members == null )
476         {
477             LOG.warn( "What do you mean there is no administrators group? This is bad news." );
478             return false;
479         }
480 
481         return members.contains( principalDn.toNormName() );
482     }
483 
484 
485     /**
486      * Gets the set of groups a user is a member of.  The groups are returned
487      * as normalized Name objects within the set.
488      *
489      * @param member the member (user) to get the groups for
490      * @return a Set of Name objects representing the groups
491      * @throws NamingException if there are problems accessing attribute  values
492      */
493     public Set<LdapDN> getGroups( String member ) throws NamingException
494     {
495         LdapDN normMember;
496 
497         try
498         {
499             normMember = parseNormalized( member );
500         }
501         catch ( NamingException e )
502         {
503             LOG
504                 .warn(
505                     "Malformed member DN.  Could not find groups for member '{}' in GroupCache. Returning empty set for groups!",
506                     member, e );
507             return EMPTY_GROUPS;
508         }
509 
510         Set<LdapDN> memberGroups = null;
511 
512         for ( String group : groups.keySet() )
513         {
514             Set<String> members = groups.get( group );
515 
516             if ( members == null )
517             {
518                 continue;
519             }
520 
521             if ( members.contains( normMember.getNormName() ) )
522             {
523                 if ( memberGroups == null )
524                 {
525                     memberGroups = new HashSet<LdapDN>();
526                 }
527 
528                 memberGroups.add( parseNormalized( group ) );
529             }
530         }
531 
532         if ( memberGroups == null )
533         {
534             return EMPTY_GROUPS;
535         }
536 
537         return memberGroups;
538     }
539 
540 
541     public boolean groupRenamed( LdapDN oldName, LdapDN newName )
542     {
543         Set<String> members = groups.remove( oldName.getNormName() );
544 
545         if ( members != null )
546         {
547             groups.put( newName.getNormName(), members );
548 
549             if ( IS_DEBUG )
550             {
551                 LOG.debug( "group cache contents after renaming '{}' :\n{}", oldName.getUpName(), groups );
552             }
553 
554             return true;
555         }
556 
557         return false;
558     }
559 }