View Javadoc

1   /*
2    *   Copyright 2004 The Apache Software Foundation
3    *
4    *   Licensed under the Apache License, Version 2.0 (the "License");
5    *   you may not use this file except in compliance with the License.
6    *   You may obtain a copy of the License at
7    *
8    *       http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *   Unless required by applicable law or agreed to in writing, software
11   *   distributed under the License is distributed on an "AS IS" BASIS,
12   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *   See the License for the specific language governing permissions and
14   *   limitations under the License.
15   *
16   */
17  package org.apache.ldap.server.schema;
18  
19  
20  import java.util.Collections;
21  import java.util.HashSet;
22  import java.util.Iterator;
23  import java.util.Map;
24  import java.util.Set;
25  
26  import javax.naming.Name;
27  import javax.naming.NamingEnumeration;
28  import javax.naming.NamingException;
29  import javax.naming.directory.*;
30  import javax.naming.ldap.LdapContext;
31  
32  import org.apache.ldap.common.filter.ExprNode;
33  import org.apache.ldap.common.filter.PresenceNode;
34  import org.apache.ldap.common.filter.SimpleNode;
35  import org.apache.ldap.common.message.LockableAttributeImpl;
36  import org.apache.ldap.common.message.LockableAttributesImpl;
37  import org.apache.ldap.common.name.LdapName;
38  import org.apache.ldap.common.schema.AttributeType;
39  import org.apache.ldap.common.schema.DITContentRule;
40  import org.apache.ldap.common.schema.DITStructureRule;
41  import org.apache.ldap.common.schema.MatchingRule;
42  import org.apache.ldap.common.schema.MatchingRuleUse;
43  import org.apache.ldap.common.schema.NameForm;
44  import org.apache.ldap.common.schema.ObjectClass;
45  import org.apache.ldap.common.schema.SchemaUtils;
46  import org.apache.ldap.common.schema.Syntax;
47  import org.apache.ldap.common.util.SingletonEnumeration;
48  import org.apache.ldap.common.util.DateUtils;
49  import org.apache.ldap.server.configuration.InterceptorConfiguration;
50  import org.apache.ldap.server.enumeration.SearchResultFilteringEnumeration;
51  import org.apache.ldap.server.enumeration.SearchResultFilter;
52  import org.apache.ldap.server.interceptor.BaseInterceptor;
53  import org.apache.ldap.server.interceptor.NextInterceptor;
54  import org.apache.ldap.server.jndi.ContextFactoryConfiguration;
55  import org.apache.ldap.server.jndi.ServerLdapContext;
56  import org.apache.ldap.server.partition.ContextPartitionNexus;
57  import org.slf4j.Logger;
58  import org.slf4j.LoggerFactory;
59  
60  
61  /***
62   * An {@link org.apache.ldap.server.interceptor.Interceptor} that manages and enforces schemas.
63   *
64   * @todo Better interceptor description required.
65   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
66   * @version $Rev: 264732 $, $Date: 2005-08-30 04:04:51 -0400 (Tue, 30 Aug 2005) $
67   */
68  public class SchemaService extends BaseInterceptor
69  {
70      private static final String[] EMPTY_STRING_ARRAY = new String[0];
71      private static final String BINARY_KEY = "java.naming.ldap.attributes.binary";
72  
73      /*** The LoggerFactory used by this Interceptor */
74      private static Logger log = LoggerFactory.getLogger( SchemaService.class );
75  
76      /***
77       * the root nexus to all database partitions
78       */
79      private ContextPartitionNexus nexus;
80  
81      /***
82       * a binary attribute tranforming filter: String -> byte[]
83       */
84      private BinaryAttributeFilter binaryAttributeFilter;
85  
86      /***
87       * the global schema object registries
88       */
89      private GlobalRegistries globalRegistries;
90  
91      private AttributeTypeRegistry attributeRegistry;
92  
93      /***
94       * subschemaSubentry attribute's value from Root DSE
95       */
96      private String subentryDn;
97      private String startUpTimeStamp;
98  
99      /***
100      * Creates a schema service interceptor.
101      */
102     public SchemaService()
103     {
104         startUpTimeStamp = DateUtils.getGeneralizedTime();
105     }
106 
107 
108     public void init( ContextFactoryConfiguration factoryCfg, InterceptorConfiguration cfg ) throws NamingException
109     {
110         this.nexus = factoryCfg.getPartitionNexus();
111         this.globalRegistries = factoryCfg.getGlobalRegistries();
112         attributeRegistry = globalRegistries.getAttributeTypeRegistry();
113         binaryAttributeFilter = new BinaryAttributeFilter();
114 
115         // stuff for dealing with subentries (garbage for now)
116         String subschemaSubentry = ( String ) nexus.getRootDSE().get( "subschemaSubentry" ).get();
117         subentryDn = new LdapName( subschemaSubentry ).toString().toLowerCase();
118     }
119 
120 
121     public void destroy()
122     {
123     }
124 
125 
126     public NamingEnumeration list( NextInterceptor nextInterceptor, Name base ) throws NamingException
127     {
128         NamingEnumeration e = nextInterceptor.list( base );
129         LdapContext ctx = getContext();
130         return new SearchResultFilteringEnumeration( e, new SearchControls(), ctx, binaryAttributeFilter );
131     }
132 
133 
134     public NamingEnumeration search( NextInterceptor nextInterceptor,
135                                      Name base, Map env, ExprNode filter,
136                                      SearchControls searchCtls ) throws NamingException
137     {
138         // check to make sure the DN searched for is a subentry
139         if ( !subentryDn.equals( base.toString() ) )
140         {
141             return nextInterceptor.search( base, env, filter, searchCtls );
142         }
143 
144         if ( searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE &&
145                 filter instanceof SimpleNode )
146         {
147             SimpleNode node = ( SimpleNode ) filter;
148 
149             if ( node.getAttribute().equalsIgnoreCase( "objectClass" ) &&
150                     node.getValue().equalsIgnoreCase( "subschema" ) &&
151                     node.getAssertionType() == SimpleNode.EQUALITY
152             )
153             {
154                 // call.setBypass( true );
155                 Attributes attrs = getSubschemaEntry( searchCtls.getReturningAttributes() );
156                 SearchResult result = new SearchResult( base.toString(), null, attrs );
157                 return new SingletonEnumeration( result );
158             }
159         }
160         else if ( searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE &&
161                 filter instanceof PresenceNode )
162         {
163             PresenceNode node = ( PresenceNode ) filter;
164 
165             if ( node.getAttribute().equalsIgnoreCase( "objectClass" ) )
166             {
167                 // call.setBypass( true );
168                 Attributes attrs = getSubschemaEntry( searchCtls.getReturningAttributes() );
169                 SearchResult result = new SearchResult( base.toString(), null, attrs );
170                 return new SingletonEnumeration( result );
171             }
172         }
173 
174         NamingEnumeration e = nextInterceptor.search( base, env, filter, searchCtls );
175 
176         if ( searchCtls.getReturningAttributes() != null )
177         {
178             return e;
179         }
180 
181         LdapContext ctx = getContext();
182         return new SearchResultFilteringEnumeration( e, searchCtls, ctx, binaryAttributeFilter );
183     }
184 
185 
186     private Attributes getSubschemaEntry( String[] ids ) throws NamingException
187     {
188         if ( ids == null )
189         {
190             ids = EMPTY_STRING_ARRAY;
191         }
192 
193         Set set = new HashSet();
194         LockableAttributesImpl attrs = new LockableAttributesImpl();
195         LockableAttributeImpl attr = null;
196 
197         for ( int ii = 0; ii < ids.length; ii++ )
198         {
199             set.add( ids[ii].toLowerCase() );
200         }
201 
202         // Check whether the set contains a plus, and use it below to include all
203         // operational attributes.  Due to RFC 3673, and issue DIREVE-228 in JIRA
204         boolean returnAllOperationalAttributes = set.contains( "+" );
205 
206         if ( returnAllOperationalAttributes || set.contains( "objectclasses" ) )
207         {
208             attr = new LockableAttributeImpl( attrs, "objectClasses" );
209             Iterator list = globalRegistries.getObjectClassRegistry().list();
210             while ( list.hasNext() )
211             {
212                 ObjectClass oc = ( ObjectClass ) list.next();
213                 attr.add( SchemaUtils.render( oc ).toString() );
214             }
215             attrs.put( attr );
216         }
217 
218         if ( returnAllOperationalAttributes || set.contains( "attributetypes" ) )
219         {
220             attr = new LockableAttributeImpl( attrs, "attributeTypes" );
221             Iterator list = globalRegistries.getAttributeTypeRegistry().list();
222             while ( list.hasNext() )
223             {
224                 AttributeType at = ( AttributeType ) list.next();
225                 attr.add( SchemaUtils.render( at ).toString() );
226             }
227             attrs.put( attr );
228         }
229 
230         if ( returnAllOperationalAttributes || set.contains( "matchingrules" ) )
231         {
232             attr = new LockableAttributeImpl( attrs, "matchingRules" );
233             Iterator list = globalRegistries.getMatchingRuleRegistry().list();
234             while ( list.hasNext() )
235             {
236                 MatchingRule mr = ( MatchingRule ) list.next();
237                 attr.add( SchemaUtils.render( mr ).toString() );
238             }
239             attrs.put( attr );
240         }
241 
242         if ( returnAllOperationalAttributes || set.contains( "matchingruleuse" ) )
243         {
244             attr = new LockableAttributeImpl( attrs, "matchingRuleUse" );
245             Iterator list = globalRegistries.getMatchingRuleUseRegistry().list();
246             while ( list.hasNext() )
247             {
248                 MatchingRuleUse mru = ( MatchingRuleUse ) list.next();
249                 attr.add( SchemaUtils.render( mru ).toString() );
250             }
251             attrs.put( attr );
252         }
253 
254         if ( returnAllOperationalAttributes || set.contains( "ldapsyntaxes" ) )
255         {
256             attr = new LockableAttributeImpl( attrs, "ldapSyntaxes" );
257             Iterator list = globalRegistries.getSyntaxRegistry().list();
258             while ( list.hasNext() )
259             {
260                 Syntax syntax = ( Syntax ) list.next();
261                 attr.add( SchemaUtils.render( syntax ).toString() );
262             }
263             attrs.put( attr );
264         }
265 
266         if ( returnAllOperationalAttributes || set.contains( "ditcontentrules" ) )
267         {
268             attr = new LockableAttributeImpl( attrs, "dITContentRules" );
269             Iterator list = globalRegistries.getDitContentRuleRegistry().list();
270             while ( list.hasNext() )
271             {
272                 DITContentRule dcr = ( DITContentRule ) list.next();
273                 attr.add( SchemaUtils.render( dcr ).toString() );
274             }
275             attrs.put( attr );
276         }
277 
278         if ( returnAllOperationalAttributes || set.contains( "ditstructurerules" ) )
279         {
280             attr = new LockableAttributeImpl( attrs, "dITStructureRules" );
281             Iterator list = globalRegistries.getDitStructureRuleRegistry().list();
282             while ( list.hasNext() )
283             {
284                 DITStructureRule dsr = ( DITStructureRule ) list.next();
285                 attr.add( SchemaUtils.render( dsr ).toString() );
286             }
287             attrs.put( attr );
288         }
289 
290         if ( returnAllOperationalAttributes || set.contains( "nameforms" ) )
291         {
292             attr = new LockableAttributeImpl( attrs, "nameForms" );
293             Iterator list = globalRegistries.getNameFormRegistry().list();
294             while ( list.hasNext() )
295             {
296                 NameForm nf = ( NameForm ) list.next();
297                 attr.add( SchemaUtils.render( nf ).toString() );
298             }
299             attrs.put( attr );
300         }
301 
302         // timeestamps are hacks for now until the schema is actually updateable these
303         // use the servers startup time stamp for both modify and create timestamps
304 
305 
306         if ( returnAllOperationalAttributes || set.contains( "createtimestamp" ) )
307         {
308             attr = new LockableAttributeImpl( attrs, "createTimestamp" );
309             attr.add( startUpTimeStamp );
310             attrs.put( attr );
311         }
312 
313         if ( returnAllOperationalAttributes || set.contains( "modifytimestamp" ) )
314         {
315             attr = new LockableAttributeImpl( attrs, "modifyTimestamp" );
316             attr.add( startUpTimeStamp );
317             attrs.put( attr );
318         }
319 
320         if ( returnAllOperationalAttributes || set.contains( "creatorsname" ) )
321         {
322             attr = new LockableAttributeImpl( attrs, "creatorsName" );
323             attr.add( ContextPartitionNexus.ADMIN_PRINCIPAL );
324             attrs.put( attr );
325         }
326 
327         if ( returnAllOperationalAttributes || set.contains( "modifiersname" ) )
328         {
329             attr = new LockableAttributeImpl( attrs, "modifiersName" );
330             attr.add( ContextPartitionNexus.ADMIN_PRINCIPAL );
331             attrs.put( attr );
332         }
333 
334         int minSetSize = 0;
335         if ( set.contains( "+" ) )
336         {
337             minSetSize++;
338         }
339         if ( set.contains( "*" ) )
340         {
341             minSetSize++;
342         }
343         if ( set.contains( "ref" ) )
344         {
345             minSetSize++;
346         }
347 
348         // add the objectClass attribute
349         if ( set.contains( "*" ) || set.contains( "objectclass" ) || set.size() == minSetSize )
350         {
351             attr = new LockableAttributeImpl( attrs, "objectClass" );
352             attr.add( "top" );
353             attr.add( "subschema" );
354             attrs.put( attr );
355         }
356 
357         // add the cn attribute as required for the RDN
358         if ( set.contains( "*" ) || set.contains( "cn" ) || set.contains( "commonname" ) || set.size() == minSetSize )
359         {
360             attrs.put( "cn", "schema" );
361         }
362 
363         return attrs;
364     }
365 
366 
367     public Attributes lookup( NextInterceptor nextInterceptor, Name name ) throws NamingException
368     {
369         Attributes result = nextInterceptor.lookup( name );
370 
371         ServerLdapContext ctx = ( ServerLdapContext ) getContext();
372         doFilter( ctx, result );
373         return result;
374     }
375 
376 
377     public Attributes lookup( NextInterceptor nextInterceptor, Name name, String[] attrIds ) throws NamingException
378     {
379         Attributes result = nextInterceptor.lookup( name, attrIds );
380         if ( result == null )
381         {
382             return null;
383         }
384 
385         ServerLdapContext ctx = ( ServerLdapContext ) getContext();
386         doFilter( ctx, result );
387         return result;
388     }
389 
390 
391     public void modify( NextInterceptor next, Name name, int modOp, Attributes mods ) throws NamingException
392     {
393         ObjectClassRegistry ocRegistry = this.globalRegistries.getObjectClassRegistry();
394 
395         if ( modOp == DirContext.REMOVE_ATTRIBUTE )
396         {
397             SchemaChecker.preventRdnChangeOnModifyRemove( name, modOp, mods );
398             Attribute ocAttr = this.nexus.lookup( name ).get( "objectClass" );
399             SchemaChecker.preventStructuralClassRemovalOnModifyRemove( ocRegistry, name, modOp, mods, ocAttr );
400         }
401 
402         if ( modOp == DirContext.REPLACE_ATTRIBUTE )
403         {
404             SchemaChecker.preventRdnChangeOnModifyReplace( name, modOp, mods );
405             SchemaChecker.preventStructuralClassRemovalOnModifyReplace( ocRegistry, name, modOp, mods );
406         }
407 
408         next.modify( name, modOp, mods );
409     }
410 
411 
412     public void modify( NextInterceptor next, Name name, ModificationItem[] mods ) throws NamingException
413     {
414         ObjectClassRegistry ocRegistry = this.globalRegistries.getObjectClassRegistry();
415 
416         for ( int ii = 0; ii < mods.length; ii++ )
417         {
418             int modOp = mods[ii].getModificationOp();
419             Attribute change = mods[ii].getAttribute();
420 
421             if ( modOp == DirContext.REMOVE_ATTRIBUTE )
422             {
423                 SchemaChecker.preventRdnChangeOnModifyRemove( name, modOp, change );
424                 Attribute ocAttr = this.nexus.lookup( name ).get( "objectClass" );
425                 SchemaChecker.preventStructuralClassRemovalOnModifyRemove( ocRegistry, name, modOp, change, ocAttr );
426             }
427 
428             if ( modOp == DirContext.REPLACE_ATTRIBUTE )
429             {
430                 SchemaChecker.preventRdnChangeOnModifyReplace( name, modOp, change );
431                 SchemaChecker.preventStructuralClassRemovalOnModifyReplace( ocRegistry, name, modOp, change );
432             }
433         }
434 
435         next.modify( name, mods );
436     }
437 
438 
439     private void doFilter( LdapContext ctx, Attributes entry )
440             throws NamingException
441     {
442         // set of AttributeType objects that are to behave as binaries
443         Set binaries;
444 
445         // construct the set for fast lookups while filtering
446         String binaryIds = ( String ) ctx.getEnvironment().get( BINARY_KEY );
447 
448         if ( binaryIds == null )
449         {
450             binaries = Collections.EMPTY_SET;
451         }
452         else
453         {
454             String[] binaryArray = binaryIds.split( " " );
455 
456             binaries = new HashSet( binaryArray.length );
457 
458             for ( int ii = 0; ii < binaryArray.length; ii++ )
459             {
460                 AttributeType type = attributeRegistry.lookup( binaryArray[ii] );
461 
462                 binaries.add( type );
463             }
464         }
465 
466         /*
467         * start converting values of attributes to byte[]s which are not
468         * human readable and those that are in the binaries set
469         */
470         NamingEnumeration list = entry.getIDs();
471 
472         while ( list.hasMore() )
473         {
474             String id = ( String ) list.next();
475 
476             AttributeType type = null;
477 
478             boolean asBinary = false;
479 
480             if ( attributeRegistry.hasAttributeType( id ) )
481             {
482                 type = attributeRegistry.lookup( id );
483             }
484 
485             if ( type != null )
486             {
487                 asBinary = !type.getSyntax().isHumanReadible();
488 
489                 asBinary = asBinary || binaries.contains( type );
490             }
491 
492             if ( asBinary )
493             {
494                 Attribute attribute = entry.get( id );
495 
496                 Attribute binary = new LockableAttributeImpl( id );
497 
498                 for ( int ii = 0; ii < attribute.size(); ii++ )
499                 {
500                     Object value = attribute.get( ii );
501 
502                     if ( value instanceof String )
503                     {
504                         binary.add( ii, ( ( String ) value ).getBytes() );
505                     }
506                     else
507                     {
508                         binary.add( ii, value );
509                     }
510                 }
511 
512                 entry.remove( id );
513 
514                 entry.put( binary );
515             }
516         }
517     }
518 
519 
520     /***
521      * A special filter over entry attributes which replaces Attribute String values with their respective byte[]
522      * representations using schema information and the value held in the JNDI environment property:
523      * <code>java.naming.ldap.attributes.binary</code>.
524      *
525      * @see <a href= "http://java.sun.com/j2se/1.4.2/docs/guide/jndi/jndi-ldap-gl.html#binary">
526      *      java.naming.ldap.attributes.binary</a>
527      */
528     private class BinaryAttributeFilter implements SearchResultFilter
529     {
530         public BinaryAttributeFilter()
531         {
532         }
533 
534 
535         public boolean accept( LdapContext ctx, SearchResult result, SearchControls controls ) throws NamingException
536         {
537             doFilter( ctx, result.getAttributes() );
538             return true;
539         }
540     }
541 }