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.operational;
21  
22  
23  import java.util.ArrayList;
24  import java.util.HashSet;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Set;
28  
29  import org.apache.directory.server.constants.ApacheSchemaConstants;
30  import org.apache.directory.server.constants.ServerDNConstants;
31  import org.apache.directory.server.core.DirectoryService;
32  import org.apache.directory.server.core.entry.ClonedServerEntry;
33  import org.apache.directory.server.core.entry.DefaultServerAttribute;
34  import org.apache.directory.server.core.entry.DefaultServerEntry;
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.entry.ServerModification;
38  import org.apache.directory.server.core.filtering.EntryFilter;
39  import org.apache.directory.server.core.filtering.EntryFilteringCursor;
40  import org.apache.directory.server.core.interceptor.BaseInterceptor;
41  import org.apache.directory.server.core.interceptor.Interceptor;
42  import org.apache.directory.server.core.interceptor.NextInterceptor;
43  import org.apache.directory.server.core.interceptor.context.AddOperationContext;
44  import org.apache.directory.server.core.interceptor.context.ListOperationContext;
45  import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
46  import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
47  import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
48  import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
49  import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
50  import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
51  import org.apache.directory.server.core.interceptor.context.SearchingOperationContext;
52  import org.apache.directory.server.schema.registries.AttributeTypeRegistry;
53  import org.apache.directory.server.schema.registries.Registries;
54  import org.apache.directory.shared.ldap.constants.SchemaConstants;
55  import org.apache.directory.shared.ldap.entry.EntryAttribute;
56  import org.apache.directory.shared.ldap.entry.Modification;
57  import org.apache.directory.shared.ldap.entry.ModificationOperation;
58  import org.apache.directory.shared.ldap.entry.Value;
59  import org.apache.directory.shared.ldap.exception.LdapSchemaViolationException;
60  import org.apache.directory.shared.ldap.message.ResultCodeEnum;
61  import org.apache.directory.shared.ldap.name.AttributeTypeAndValue;
62  import org.apache.directory.shared.ldap.name.LdapDN;
63  import org.apache.directory.shared.ldap.name.Rdn;
64  import org.apache.directory.shared.ldap.schema.AttributeType;
65  import org.apache.directory.shared.ldap.schema.UsageEnum;
66  import org.apache.directory.shared.ldap.util.DateUtils;
67  import org.slf4j.Logger;
68  import org.slf4j.LoggerFactory;
69   
70  
71  /**
72   * An {@link Interceptor} that adds or modifies the default attributes
73   * of entries. There are four default attributes for now;
74   * <tt>'creatorsName'</tt>, <tt>'createTimestamp'</tt>, <tt>'modifiersName'</tt>,
75   * and <tt>'modifyTimestamp'</tt>.
76   *
77   * @org.apache.xbean.XBean
78   *
79   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
80   * @version $Rev: 687163 $, $Date: 2008-08-19 23:34:39 +0200 (Di, 19 Aug 2008) $
81   */
82  public class OperationalAttributeInterceptor extends BaseInterceptor
83  {
84      /** The LoggerFactory used by this Interceptor */
85      private static Logger LOG = LoggerFactory.getLogger( OperationalAttributeInterceptor.class );
86  
87      private final EntryFilter DENORMALIZING_SEARCH_FILTER = new EntryFilter()
88      {
89          public boolean accept( SearchingOperationContext operation, ClonedServerEntry serverEntry ) 
90              throws Exception
91          {
92              if ( operation.getSearchControls().getReturningAttributes() == null )
93              {
94                  return true;
95              }
96              
97              return filterDenormalized( serverEntry );
98          }
99      };
100 
101     /**
102      * the database search result filter to register with filter service
103      */
104     private final EntryFilter SEARCH_FILTER = new EntryFilter()
105     {
106         public boolean accept( SearchingOperationContext operation, ClonedServerEntry entry )
107             throws Exception
108         {
109             return operation.getSearchControls().getReturningAttributes() != null 
110                 || filterOperationalAttributes( entry );
111         }
112     };
113 
114 
115     private AttributeTypeRegistry atRegistry;
116 
117     private DirectoryService service;
118 
119     private LdapDN subschemaSubentryDn;
120     
121     /** The registries */
122     private Registries registries;
123     
124     private static AttributeType CREATE_TIMESTAMP_ATTRIBUTE_TYPE;
125 
126 
127     /**
128      * Creates the operational attribute management service interceptor.
129      */
130     public OperationalAttributeInterceptor()
131     {
132     }
133 
134 
135     public void init( DirectoryService directoryService ) throws Exception
136     {
137         service = directoryService;
138         registries = directoryService.getRegistries();
139         atRegistry = registries.getAttributeTypeRegistry();
140 
141         // stuff for dealing with subentries (garbage for now)
142         Value<?> subschemaSubentry = service.getPartitionNexus()
143                 .getRootDSE( null ).get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ).get();
144         subschemaSubentryDn = new LdapDN( (String)subschemaSubentry.get() );
145         subschemaSubentryDn.normalize( atRegistry.getNormalizerMapping() );
146         
147         CREATE_TIMESTAMP_ATTRIBUTE_TYPE = atRegistry.lookup( SchemaConstants.CREATE_TIMESTAMP_AT );
148     }
149 
150 
151     public void destroy()
152     {
153     }
154 
155 
156     /**
157      * Adds extra operational attributes to the entry before it is added.
158      */
159     public void add( NextInterceptor nextInterceptor, AddOperationContext opContext )
160         throws Exception
161     {
162         String principal = getPrincipal().getName();
163         
164         ServerEntry entry = opContext.getEntry();
165 
166         entry.put( SchemaConstants.CREATORS_NAME_AT, principal );
167         
168         EntryAttribute createTimeStamp = new DefaultServerAttribute( CREATE_TIMESTAMP_ATTRIBUTE_TYPE );
169         
170         if ( opContext.getEntry().contains( createTimeStamp ) )
171         {
172             // As we already have a CreateTimeStamp value in the context, use it, but only if
173             // the principal is admin
174             if ( opContext.getSession().getAuthenticatedPrincipal().getName().equals( 
175                 ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED ))
176             {
177                 entry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
178             }
179             else
180             {
181                 String message = "The CreateTimeStamp attribute cannot be created by a user";
182                 LOG.error( message );
183                 throw new LdapSchemaViolationException( message, ResultCodeEnum.INSUFFICIENT_ACCESS_RIGHTS );
184             }
185         }
186         else
187         {
188             entry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
189         }
190         
191         nextInterceptor.add( opContext );
192     }
193 
194 
195     public void modify( NextInterceptor nextInterceptor, ModifyOperationContext opContext )
196         throws Exception
197     {
198         nextInterceptor.modify( opContext );
199         
200         if ( opContext.getDn().getNormName().equals( subschemaSubentryDn.getNormName() ) ) 
201         {
202             return;
203         }
204 
205         // -------------------------------------------------------------------
206         // Add the operational attributes for the modifier first
207         // -------------------------------------------------------------------
208         
209         List<Modification> modItemList = new ArrayList<Modification>(2);
210         
211         AttributeType modifiersNameAt = atRegistry.lookup( SchemaConstants.MODIFIERS_NAME_AT );
212         ServerAttribute attribute = new DefaultServerAttribute( 
213             SchemaConstants.MODIFIERS_NAME_AT,
214             modifiersNameAt, 
215             getPrincipal().getName());
216 
217         Modification modifiers = new ServerModification( ModificationOperation.REPLACE_ATTRIBUTE, attribute );
218         //modifiers.setServerModified();
219         modItemList.add( modifiers );
220         
221         AttributeType modifyTimeStampAt = atRegistry.lookup( SchemaConstants.MODIFY_TIMESTAMP_AT );
222         attribute = new DefaultServerAttribute( 
223             SchemaConstants.MODIFY_TIMESTAMP_AT,
224             modifyTimeStampAt,
225             DateUtils.getGeneralizedTime() );
226         
227         Modification timestamp = new ServerModification( ModificationOperation.REPLACE_ATTRIBUTE, attribute );
228         //timestamp.setServerModified();
229         modItemList.add( timestamp );
230 
231         // -------------------------------------------------------------------
232         // Make the modify() call happen
233         // -------------------------------------------------------------------
234 
235         ModifyOperationContext newModify = new ModifyOperationContext( opContext.getSession(), 
236             opContext.getDn(), modItemList );
237         service.getPartitionNexus().modify( newModify );
238     }
239 
240 
241     public void rename( NextInterceptor nextInterceptor, RenameOperationContext opContext )
242         throws Exception
243     {
244         nextInterceptor.rename( opContext );
245 
246         // add operational attributes after call in case the operation fails
247         ServerEntry serverEntry = new DefaultServerEntry( registries, opContext.getDn() );
248         serverEntry.put( SchemaConstants.MODIFIERS_NAME_AT, getPrincipal().getName() );
249         serverEntry.put( SchemaConstants.MODIFY_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
250 
251         LdapDN newDn = ( LdapDN ) opContext.getDn().clone();
252         newDn.remove( opContext.getDn().size() - 1 );
253         newDn.add( opContext.getNewRdn() );
254         newDn.normalize( atRegistry.getNormalizerMapping() );
255         
256         List<Modification> items = ModifyOperationContext.createModItems( serverEntry, ModificationOperation.REPLACE_ATTRIBUTE );
257 
258         ModifyOperationContext newModify = new ModifyOperationContext( opContext.getSession(), newDn, items );
259         
260         service.getPartitionNexus().modify( newModify );
261     }
262 
263 
264     public void move( NextInterceptor nextInterceptor, MoveOperationContext opContext ) throws Exception
265     {
266         nextInterceptor.move( opContext );
267 
268         // add operational attributes after call in case the operation fails
269         ServerEntry serverEntry = new DefaultServerEntry( registries, opContext.getDn() );
270         serverEntry.put( SchemaConstants.MODIFIERS_NAME_AT, getPrincipal().getName() );
271         serverEntry.put( SchemaConstants.MODIFY_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
272 
273         List<Modification> items = ModifyOperationContext.createModItems( serverEntry, ModificationOperation.REPLACE_ATTRIBUTE );
274 
275 
276         ModifyOperationContext newModify = 
277             new ModifyOperationContext( opContext.getSession(), opContext.getParent(), items );
278         
279         service.getPartitionNexus().modify( newModify );
280     }
281 
282 
283     public void moveAndRename( NextInterceptor nextInterceptor, MoveAndRenameOperationContext opContext )
284         throws Exception
285     {
286         nextInterceptor.moveAndRename( opContext );
287 
288         // add operational attributes after call in case the operation fails
289         ServerEntry serverEntry = new DefaultServerEntry( registries, opContext.getDn() );
290         serverEntry.put( SchemaConstants.MODIFIERS_NAME_AT, getPrincipal().getName() );
291         serverEntry.put( SchemaConstants.MODIFY_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
292 
293         List<Modification> items = ModifyOperationContext.createModItems( serverEntry, ModificationOperation.REPLACE_ATTRIBUTE );
294 
295         ModifyOperationContext newModify = 
296             new ModifyOperationContext( opContext.getSession(), opContext.getParent(), items );
297         
298         service.getPartitionNexus().modify( newModify );
299     }
300 
301 
302     public ClonedServerEntry lookup( NextInterceptor nextInterceptor, LookupOperationContext opContext ) throws Exception
303     {
304         ClonedServerEntry result = nextInterceptor.lookup( opContext );
305         
306         if ( result == null )
307         {
308             return null;
309         }
310 
311         if ( opContext.getAttrsId() == null )
312         {
313             filterOperationalAttributes( result );
314         }
315         else
316         {
317             filter( opContext, result );
318         }
319         
320         return result;
321     }
322 
323 
324     public EntryFilteringCursor list( NextInterceptor nextInterceptor, ListOperationContext opContext ) throws Exception
325     {
326         EntryFilteringCursor cursor = nextInterceptor.list( opContext );
327         cursor.addEntryFilter( SEARCH_FILTER );
328         return cursor;
329     }
330 
331 
332     public EntryFilteringCursor search( NextInterceptor nextInterceptor, SearchOperationContext opContext ) throws Exception
333     {
334         EntryFilteringCursor cursor = nextInterceptor.search( opContext );
335         
336         if ( opContext.isAllOperationalAttributes() || 
337              ( opContext.getReturningAttributes() != null && ! opContext.getReturningAttributes().isEmpty() ) )
338         {
339             if ( service.isDenormalizeOpAttrsEnabled() )
340             {
341                 cursor.addEntryFilter( DENORMALIZING_SEARCH_FILTER );
342             }
343                 
344             return cursor;
345         }
346 
347         cursor.addEntryFilter( SEARCH_FILTER );
348         return cursor;
349     }
350 
351 
352     /**
353      * Filters out the operational attributes within a search results attributes.  The attributes are directly
354      * modified.
355      *
356      * @param attributes the resultant attributes to filter
357      * @return true always
358      * @throws Exception if there are failures in evaluation
359      */
360     private boolean filterOperationalAttributes( ServerEntry attributes ) throws Exception
361     {
362         Set<AttributeType> removedAttributes = new HashSet<AttributeType>();
363 
364         // Build a list of attributeType to remove
365         for ( AttributeType attributeType:attributes.getAttributeTypes() )
366         {
367             if ( attributeType.getUsage() != UsageEnum.USER_APPLICATIONS )
368             {
369                 removedAttributes.add( attributeType );
370             }
371         }
372         
373         // Now remove the attributes which are not USERs
374         for ( AttributeType attributeType:removedAttributes )
375         {
376             attributes.removeAttributes( attributeType );
377         }
378         
379         return true;
380     }
381 
382 
383     private void filter( LookupOperationContext lookupContext, ServerEntry entry ) throws Exception
384     {
385         LdapDN dn = lookupContext.getDn();
386         List<String> ids = lookupContext.getAttrsId();
387         
388         // still need to protect against returning op attrs when ids is null
389         if ( ids == null || ids.isEmpty() )
390         {
391             filterOperationalAttributes( entry );
392             return;
393         }
394 
395         Set<AttributeType> attributeTypes = entry.getAttributeTypes();
396 
397         if ( dn.size() == 0 )
398         {
399             for ( AttributeType attributeType:attributeTypes )
400             {
401                 if ( !ids.contains( attributeType.getOid() ) )
402                 {
403                     entry.removeAttributes( attributeType );
404                 }
405             }
406         }
407 
408         denormalizeEntryOpAttrs( entry );
409         
410         // do nothing past here since this explicity specifies which
411         // attributes to include - backends will automatically populate
412         // with right set of attributes using ids array
413     }
414 
415     
416     public void denormalizeEntryOpAttrs( ServerEntry entry ) throws Exception
417     {
418         if ( service.isDenormalizeOpAttrsEnabled() )
419         {
420             EntryAttribute attr = entry.get( SchemaConstants.CREATORS_NAME_AT );
421 
422             if ( attr != null )
423             {
424                 LdapDN creatorsName = new LdapDN( attr.getString() );
425                 
426                 attr.clear();
427                 attr.add( denormalizeTypes( creatorsName ).getUpName() );
428             }
429             
430             attr = entry.get( SchemaConstants.MODIFIERS_NAME_AT );
431             
432             if ( attr != null )
433             {
434                 LdapDN modifiersName = new LdapDN( attr.getString() );
435 
436                 attr.clear();
437                 attr.add( denormalizeTypes( modifiersName ).getUpName() );
438             }
439 
440             attr = entry.get( ApacheSchemaConstants.SCHEMA_MODIFIERS_NAME_AT );
441             
442             if ( attr != null )
443             {
444                 LdapDN modifiersName = new LdapDN( attr.getString() );
445 
446                 attr.clear();
447                 attr.add( denormalizeTypes( modifiersName ).getUpName() );
448             }
449         }
450     }
451 
452     
453     /**
454      * Does not create a new DN but alters existing DN by using the first
455      * short name for an attributeType definition.
456      * 
457      * @param dn the normalized distinguished name
458      * @return the distinuished name denormalized
459      * @throws Exception if there are problems denormalizing
460      */
461     public LdapDN denormalizeTypes( LdapDN dn ) throws Exception
462     {
463         LdapDN newDn = new LdapDN();
464         
465         for ( int ii = 0; ii < dn.size(); ii++ )
466         {
467             Rdn rdn = dn.getRdn( ii );
468             if ( rdn.size() == 0 )
469             {
470                 newDn.add( new Rdn() );
471                 continue;
472             }
473             else if ( rdn.size() == 1 )
474             {
475                 String name = atRegistry.lookup( rdn.getNormType() ).getName();
476                 String value = (String)rdn.getAtav().getNormValue(); 
477                 newDn.add( new Rdn( name, name, value, value ) );
478                 continue;
479             }
480 
481             // below we only process multi-valued rdns
482             StringBuffer buf = new StringBuffer();
483         
484             for ( Iterator<AttributeTypeAndValue> atavs = rdn.iterator(); atavs.hasNext(); /**/ )
485             {
486                 AttributeTypeAndValue atav = atavs.next();
487                 String type = atRegistry.lookup( rdn.getNormType() ).getName();
488                 buf.append( type ).append( '=' ).append( atav.getNormValue() );
489                 
490                 if ( atavs.hasNext() )
491                 {
492                     buf.append( '+' );
493                 }
494             }
495             
496             newDn.add( new Rdn(buf.toString()) );
497         }
498         
499         return newDn;
500     }
501 
502 
503     private boolean filterDenormalized( ServerEntry entry ) throws Exception
504     {
505         denormalizeEntryOpAttrs( entry );
506         return true;
507     }
508 }