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.schema;
021
022
023 import java.io.UnsupportedEncodingException;
024 import java.util.ArrayList;
025 import java.util.HashMap;
026 import java.util.HashSet;
027 import java.util.Iterator;
028 import java.util.List;
029 import java.util.Map;
030 import java.util.Set;
031 import java.util.concurrent.ConcurrentHashMap;
032
033 import javax.naming.directory.SearchControls;
034
035 import org.apache.directory.server.constants.ServerDNConstants;
036 import org.apache.directory.server.core.DirectoryService;
037 import org.apache.directory.server.core.entry.ClonedServerEntry;
038 import org.apache.directory.server.core.filtering.BaseEntryFilteringCursor;
039 import org.apache.directory.server.core.filtering.EntryFilter;
040 import org.apache.directory.server.core.filtering.EntryFilteringCursor;
041 import org.apache.directory.server.core.interceptor.BaseInterceptor;
042 import org.apache.directory.server.core.interceptor.NextInterceptor;
043 import org.apache.directory.server.core.interceptor.context.AddOperationContext;
044 import org.apache.directory.server.core.interceptor.context.ListOperationContext;
045 import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
046 import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
047 import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
048 import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
049 import org.apache.directory.server.core.interceptor.context.SearchingOperationContext;
050 import org.apache.directory.server.core.partition.PartitionNexus;
051 import org.apache.directory.server.i18n.I18n;
052 import org.apache.directory.shared.ldap.codec.controls.CascadeControl;
053 import org.apache.directory.shared.ldap.constants.MetaSchemaConstants;
054 import org.apache.directory.shared.ldap.constants.SchemaConstants;
055 import org.apache.directory.shared.ldap.cursor.EmptyCursor;
056 import org.apache.directory.shared.ldap.cursor.SingletonCursor;
057 import org.apache.directory.shared.ldap.entry.BinaryValue;
058 import org.apache.directory.shared.ldap.entry.StringValue;
059 import org.apache.directory.shared.ldap.entry.DefaultServerAttribute;
060 import org.apache.directory.shared.ldap.entry.Entry;
061 import org.apache.directory.shared.ldap.entry.EntryAttribute;
062 import org.apache.directory.shared.ldap.entry.Modification;
063 import org.apache.directory.shared.ldap.entry.ModificationOperation;
064 import org.apache.directory.shared.ldap.entry.ServerEntry;
065 import org.apache.directory.shared.ldap.entry.ServerModification;
066 import org.apache.directory.shared.ldap.entry.Value;
067 import org.apache.directory.shared.ldap.exception.LdapAttributeInUseException;
068 import org.apache.directory.shared.ldap.exception.LdapException;
069 import org.apache.directory.shared.ldap.exception.LdapInvalidAttributeTypeException;
070 import org.apache.directory.shared.ldap.exception.LdapInvalidAttributeValueException;
071 import org.apache.directory.shared.ldap.exception.LdapNoPermissionException;
072 import org.apache.directory.shared.ldap.exception.LdapNoSuchAttributeException;
073 import org.apache.directory.shared.ldap.exception.LdapSchemaViolationException;
074 import org.apache.directory.shared.ldap.filter.ApproximateNode;
075 import org.apache.directory.shared.ldap.filter.AssertionNode;
076 import org.apache.directory.shared.ldap.filter.BranchNode;
077 import org.apache.directory.shared.ldap.filter.EqualityNode;
078 import org.apache.directory.shared.ldap.filter.ExprNode;
079 import org.apache.directory.shared.ldap.filter.ExtensibleNode;
080 import org.apache.directory.shared.ldap.filter.GreaterEqNode;
081 import org.apache.directory.shared.ldap.filter.LessEqNode;
082 import org.apache.directory.shared.ldap.filter.PresenceNode;
083 import org.apache.directory.shared.ldap.filter.ScopeNode;
084 import org.apache.directory.shared.ldap.filter.SimpleNode;
085 import org.apache.directory.shared.ldap.filter.SubstringNode;
086 import org.apache.directory.shared.ldap.message.ResultCodeEnum;
087 import org.apache.directory.shared.ldap.name.AVA;
088 import org.apache.directory.shared.ldap.name.DN;
089 import org.apache.directory.shared.ldap.name.RDN;
090 import org.apache.directory.shared.ldap.schema.AttributeType;
091 import org.apache.directory.shared.ldap.schema.AttributeTypeOptions;
092 import org.apache.directory.shared.ldap.schema.ObjectClass;
093 import org.apache.directory.shared.ldap.schema.ObjectClassTypeEnum;
094 import org.apache.directory.shared.ldap.schema.SchemaManager;
095 import org.apache.directory.shared.ldap.schema.SyntaxChecker;
096 import org.apache.directory.shared.ldap.schema.UsageEnum;
097 import org.apache.directory.shared.ldap.schema.registries.OidRegistry;
098 import org.apache.directory.shared.ldap.schema.registries.Schema;
099 import org.apache.directory.shared.ldap.schema.registries.SchemaLoader;
100 import org.apache.directory.shared.ldap.schema.syntaxCheckers.OctetStringSyntaxChecker;
101 import org.slf4j.Logger;
102 import org.slf4j.LoggerFactory;
103
104
105 /**
106 * An {@link org.apache.directory.server.core.interceptor.Interceptor} that manages and enforces schemas.
107 *
108 * @todo Better interceptor description required.
109
110 * @org.apache.xbean.XBean
111 *
112 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
113 * @version $Rev: 928945 $, $Date: 2010-03-30 02:59:49 +0300 (Tue, 30 Mar 2010) $
114 */
115 public class SchemaInterceptor extends BaseInterceptor
116 {
117 /** The LoggerFactory used by this Interceptor */
118 private static Logger LOG = LoggerFactory.getLogger( SchemaInterceptor.class );
119
120 private static final String[] SCHEMA_SUBENTRY_RETURN_ATTRIBUTES = new String[]
121 {
122 SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES,
123 SchemaConstants.ALL_USER_ATTRIBUTES
124 };
125
126 /** Speedup for logs */
127 private static final boolean IS_DEBUG = LOG.isDebugEnabled();
128
129 /**
130 * the root nexus to all database partitions
131 */
132 private PartitionNexus nexus;
133
134 /**
135 * a binary attribute tranforming filter: String -> byte[]
136 */
137 private BinaryAttributeFilter binaryAttributeFilter;
138
139 private TopFilter topFilter;
140
141 private List<EntryFilter> filters = new ArrayList<EntryFilter>();
142
143 /** the global schema object SchemaManager */
144 private SchemaManager schemaManager;
145
146 /** A global reference to the ObjectClass attributeType */
147 private AttributeType OBJECT_CLASS;
148
149 /** A normalized form for the SubschemaSubentry DN */
150 private String subschemaSubentryDnNorm;
151
152 /** The SubschemaSubentry DN */
153 private DN subschemaSubentryDn;
154
155 /**
156 * the normalized name for the schema modification attributes
157 */
158 private DN schemaModificationAttributesDN;
159
160 /** The schema manager */
161 private SchemaSubentryManager schemaSubEntryManager;
162
163 private SchemaService schemaService;
164
165 /** the base DN (normalized) of the schema partition */
166 private DN schemaBaseDN;
167
168 /** A map used to store all the objectClasses superiors */
169 private Map<String, List<ObjectClass>> superiors;
170
171 /** A map used to store all the objectClasses may attributes */
172 private Map<String, List<AttributeType>> allMay;
173
174 /** A map used to store all the objectClasses must */
175 private Map<String, List<AttributeType>> allMust;
176
177 /** A map used to store all the objectClasses allowed attributes (may + must) */
178 private Map<String, List<AttributeType>> allowed;
179
180 private static AttributeType MODIFIERS_NAME_ATTRIBUTE_TYPE;
181 private static AttributeType MODIFY_TIMESTAMP_ATTRIBUTE_TYPE;
182
183 /**
184 * Initialize the Schema Service
185 *
186 * @param directoryService the directory service core
187 * @throws Exception if there are problems during initialization
188 */
189 public void init( DirectoryService directoryService ) throws Exception
190 {
191 if ( IS_DEBUG )
192 {
193 LOG.debug( "Initializing SchemaInterceptor..." );
194 }
195
196 nexus = directoryService.getPartitionNexus();
197 schemaManager = directoryService.getSchemaManager();
198 OBJECT_CLASS = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.OBJECT_CLASS_AT );
199 binaryAttributeFilter = new BinaryAttributeFilter();
200 topFilter = new TopFilter();
201 filters.add( binaryAttributeFilter );
202 filters.add( topFilter );
203
204 schemaBaseDN = new DN( SchemaConstants.OU_SCHEMA );
205 schemaBaseDN.normalize( schemaManager.getNormalizerMapping() );
206 schemaService = directoryService.getSchemaService();
207
208 // stuff for dealing with subentries (garbage for now)
209 Value<?> subschemaSubentry = nexus.getRootDSE( null ).get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ).get();
210 subschemaSubentryDn = new DN( subschemaSubentry.getString() );
211 subschemaSubentryDn.normalize( schemaManager.getNormalizerMapping() );
212 subschemaSubentryDnNorm = subschemaSubentryDn.getNormName();
213
214 schemaModificationAttributesDN = new DN( ServerDNConstants.SCHEMA_MODIFICATIONS_DN );
215 schemaModificationAttributesDN.normalize( schemaManager.getNormalizerMapping() );
216
217 computeSuperiors();
218
219 // Initialize the schema manager
220 SchemaLoader loader = schemaService.getSchemaPartition().getSchemaManager().getLoader();
221 schemaSubEntryManager = new SchemaSubentryManager( schemaManager, loader );
222
223 MODIFIERS_NAME_ATTRIBUTE_TYPE = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.MODIFIERS_NAME_AT );
224 MODIFY_TIMESTAMP_ATTRIBUTE_TYPE = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.MODIFY_TIMESTAMP_AT );
225
226 if ( IS_DEBUG )
227 {
228 LOG.debug( "SchemaInterceptor Initialized !" );
229 }
230 }
231
232
233 /**
234 * Compute the MUST attributes for an objectClass. This method gather all the
235 * MUST from all the objectClass and its superors.
236 *
237 * @param atSeen ???
238 * @param objectClass the object class to gather MUST attributes for
239 * @throws Exception if there are problems resolving schema entitites
240 */
241 private void computeMustAttributes( ObjectClass objectClass, Set<String> atSeen ) throws Exception
242 {
243 List<ObjectClass> parents = superiors.get( objectClass.getOid() );
244
245 List<AttributeType> mustList = new ArrayList<AttributeType>();
246 List<AttributeType> allowedList = new ArrayList<AttributeType>();
247 Set<String> mustSeen = new HashSet<String>();
248
249 allMust.put( objectClass.getOid(), mustList );
250 allowed.put( objectClass.getOid(), allowedList );
251
252 for ( ObjectClass parent : parents )
253 {
254 List<AttributeType> mustParent = parent.getMustAttributeTypes();
255
256 if ( ( mustParent != null ) && ( mustParent.size() != 0 ) )
257 {
258 for ( AttributeType attributeType : mustParent )
259 {
260 String oid = attributeType.getOid();
261
262 if ( !mustSeen.contains( oid ) )
263 {
264 mustSeen.add( oid );
265 mustList.add( attributeType );
266 allowedList.add( attributeType );
267 atSeen.add( attributeType.getOid() );
268 }
269 }
270 }
271 }
272 }
273
274
275 /**
276 * Compute the MAY attributes for an objectClass. This method gather all the
277 * MAY from all the objectClass and its superors.
278 *
279 * The allowed attributes is also computed, it's the union of MUST and MAY
280 *
281 * @param atSeen ???
282 * @param objectClass the object class to get all the MAY attributes for
283 * @throws Exception with problems accessing registries
284 */
285 private void computeMayAttributes( ObjectClass objectClass, Set<String> atSeen ) throws Exception
286 {
287 List<ObjectClass> parents = superiors.get( objectClass.getOid() );
288
289 List<AttributeType> mayList = new ArrayList<AttributeType>();
290 Set<String> maySeen = new HashSet<String>();
291 List<AttributeType> allowedList = allowed.get( objectClass.getOid() );
292
293 allMay.put( objectClass.getOid(), mayList );
294
295 for ( ObjectClass parent : parents )
296 {
297 List<AttributeType> mustParent = parent.getMustAttributeTypes();
298
299 if ( ( mustParent != null ) && ( mustParent.size() != 0 ) )
300 {
301 for ( AttributeType attributeType : mustParent )
302 {
303 String oid = attributeType.getOid();
304
305 if ( !maySeen.contains( oid ) )
306 {
307 maySeen.add( oid );
308 mayList.add( attributeType );
309
310 if ( !atSeen.contains( oid ) )
311 {
312 allowedList.add( attributeType );
313 }
314 }
315 }
316 }
317 }
318 }
319
320
321 /**
322 * Recursively compute all the superiors of an object class. For instance, considering
323 * 'inetOrgPerson', it's direct superior is 'organizationalPerson', which direct superior
324 * is 'Person', which direct superior is 'top'.
325 *
326 * As a result, we will gather all of these three ObjectClasses in 'inetOrgPerson' ObjectClasse
327 * superiors.
328 */
329 private void computeOCSuperiors( ObjectClass objectClass, List<ObjectClass> superiors, Set<String> ocSeen )
330 throws Exception
331 {
332 List<ObjectClass> parents = objectClass.getSuperiors();
333
334 // Loop on all the objectClass superiors
335 if ( ( parents != null ) && ( parents.size() != 0 ) )
336 {
337 for ( ObjectClass parent : parents )
338 {
339 // Top is not added
340 if ( SchemaConstants.TOP_OC.equals( parent.getName() ) )
341 {
342 continue;
343 }
344
345 // For each one, recurse
346 computeOCSuperiors( parent, superiors, ocSeen );
347
348 String oid = parent.getOid();
349
350 if ( !ocSeen.contains( oid ) )
351 {
352 superiors.add( parent );
353 ocSeen.add( oid );
354 }
355 }
356 }
357 }
358
359
360 /**
361 * Compute the superiors and MUST/MAY attributes for a specific
362 * ObjectClass
363 */
364 private void computeSuperior( ObjectClass objectClass ) throws Exception
365 {
366 List<ObjectClass> ocSuperiors = new ArrayList<ObjectClass>();
367
368 superiors.put( objectClass.getOid(), ocSuperiors );
369
370 computeOCSuperiors( objectClass, ocSuperiors, new HashSet<String>() );
371
372 Set<String> atSeen = new HashSet<String>();
373 computeMustAttributes( objectClass, atSeen );
374 computeMayAttributes( objectClass, atSeen );
375
376 superiors.put( objectClass.getName(), ocSuperiors );
377 }
378
379
380 /**
381 * Compute all ObjectClasses superiors, MAY and MUST attributes.
382 * @throws Exception
383 */
384 private void computeSuperiors() throws Exception
385 {
386 Iterator<ObjectClass> objectClasses = schemaManager.getObjectClassRegistry().iterator();
387 superiors = new ConcurrentHashMap<String, List<ObjectClass>>();
388 allMust = new ConcurrentHashMap<String, List<AttributeType>>();
389 allMay = new ConcurrentHashMap<String, List<AttributeType>>();
390 allowed = new ConcurrentHashMap<String, List<AttributeType>>();
391
392 while ( objectClasses.hasNext() )
393 {
394 ObjectClass objectClass = objectClasses.next();
395 computeSuperior( objectClass );
396 }
397 }
398
399
400 public EntryFilteringCursor list( NextInterceptor nextInterceptor, ListOperationContext opContext )
401 throws Exception
402 {
403 EntryFilteringCursor cursor = nextInterceptor.list( opContext );
404 cursor.addEntryFilter( binaryAttributeFilter );
405 return cursor;
406 }
407
408
409 /**
410 * Remove all unknown attributes from the searchControls, to avoid an exception.
411 *
412 * RFC 2251 states that :
413 * " Attributes MUST be named at most once in the list, and are returned "
414 * " at most once in an entry. "
415 * " If there are attribute descriptions in "
416 * " the list which are not recognized, they are ignored by the server."
417 *
418 * @param searchCtls The SearchControls we will filter
419 */
420 private void filterAttributesToReturn( SearchControls searchCtls )
421 {
422 String[] attributes = searchCtls.getReturningAttributes();
423
424 if ( ( attributes == null ) || ( attributes.length == 0 ) )
425 {
426 // We have no attributes, that means "*" (all users attributes)
427 searchCtls.setReturningAttributes( SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY );
428 return;
429 }
430
431 Map<String, String> filteredAttrs = new HashMap<String, String>();
432 boolean hasNoAttribute = false;
433 boolean hasAttributes = false;
434
435 for ( String attribute : attributes )
436 {
437 // Skip special attributes
438 if ( ( SchemaConstants.ALL_USER_ATTRIBUTES.equals( attribute ) )
439 || ( SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES.equals( attribute ) )
440 || ( SchemaConstants.NO_ATTRIBUTE.equals( attribute ) ) )
441 {
442 if ( !filteredAttrs.containsKey( attribute ) )
443 {
444 filteredAttrs.put( attribute, attribute );
445 }
446
447 if ( SchemaConstants.NO_ATTRIBUTE.equals( attribute ) )
448 {
449 hasNoAttribute = true;
450 }
451 else
452 {
453 hasAttributes = true;
454 }
455
456 continue;
457 }
458
459 try
460 {
461 // Check that the attribute is declared
462 if ( schemaManager.getAttributeTypeRegistry().contains( attribute ) )
463 {
464 String oid = schemaManager.getAttributeTypeRegistry().getOidByName( attribute );
465
466 // The attribute must be an AttributeType
467 if ( schemaManager.getAttributeTypeRegistry().contains( oid ) )
468 {
469 if ( !filteredAttrs.containsKey( oid ) )
470 {
471 // Ok, we can add the attribute to the list of filtered attributes
472 filteredAttrs.put( oid, attribute );
473 }
474 }
475 }
476
477 hasAttributes = true;
478 }
479 catch ( Exception ne )
480 {
481 /* Do nothing, the attribute does not exist */
482 }
483 }
484
485 // Treat a special case : if we have an attribute and "1.1", then discard "1.1"
486 if ( hasAttributes && hasNoAttribute )
487 {
488 filteredAttrs.remove( SchemaConstants.NO_ATTRIBUTE );
489 }
490
491 // If we still have the same attribute number, then we can just get out the method
492 if ( filteredAttrs.size() == attributes.length )
493 {
494 return;
495 }
496
497 // Deal with the special case where the attribute list is now empty
498 if ( filteredAttrs.size() == 0 )
499 {
500 // We just have to pass the special 1.1 attribute,
501 // as we don't want to return any attribute
502 searchCtls.setReturningAttributes( SchemaConstants.NO_ATTRIBUTE_ARRAY );
503 return;
504 }
505
506 // Some attributes have been removed. let's modify the searchControl
507 String[] newAttributesList = new String[filteredAttrs.size()];
508
509 int pos = 0;
510
511 for ( String key : filteredAttrs.keySet() )
512 {
513 newAttributesList[pos++] = filteredAttrs.get( key );
514 }
515
516 searchCtls.setReturningAttributes( newAttributesList );
517 }
518
519
520 private Value<?> convert( String id, Object value ) throws Exception
521 {
522 AttributeType at = schemaManager.lookupAttributeTypeRegistry( id );
523
524 if ( at.getSyntax().isHumanReadable() )
525 {
526 if ( value instanceof byte[] )
527 {
528 try
529 {
530 return new StringValue( new String( ( byte[] ) value, "UTF-8" ) );
531 }
532 catch ( UnsupportedEncodingException uee )
533 {
534 String message = I18n.err( I18n.ERR_47 );
535 LOG.error( message );
536 throw new LdapException( message );
537 }
538 }
539 }
540 else
541 {
542 if ( value instanceof String )
543 {
544 try
545 {
546 return new BinaryValue( ( ( String ) value ).getBytes( "UTF-8" ) );
547 }
548 catch ( UnsupportedEncodingException uee )
549 {
550 String message = I18n.err( I18n.ERR_48 );
551 LOG.error( message );
552 throw new LdapException( message );
553 }
554 }
555 }
556
557 return null;
558 }
559
560
561 /**
562 * Check that the filter values are compatible with the AttributeType. Typically,
563 * a HumanReadible filter should have a String value. The substring filter should
564 * not be used with binary attributes.
565 */
566 private void checkFilter( ExprNode filter ) throws Exception
567 {
568 if ( filter == null )
569 {
570 String message = I18n.err( I18n.ERR_49 );
571 LOG.error( message );
572 throw new LdapException( message );
573 }
574
575 if ( filter.isLeaf() )
576 {
577 if ( filter instanceof EqualityNode )
578 {
579 EqualityNode node = ( ( EqualityNode ) filter );
580 Object value = node.getValue();
581
582 Value<?> newValue = convert( node.getAttribute(), value );
583
584 if ( newValue != null )
585 {
586 node.setValue( newValue );
587 }
588 }
589 else if ( filter instanceof SubstringNode )
590 {
591 SubstringNode node = ( ( SubstringNode ) filter );
592
593 if ( !schemaManager.lookupAttributeTypeRegistry( node.getAttribute() ).getSyntax().isHumanReadable() )
594 {
595 String message = I18n.err( I18n.ERR_50 );
596 LOG.error( message );
597 throw new LdapException( message );
598 }
599 }
600 else if ( filter instanceof PresenceNode )
601 {
602 // Nothing to do
603 }
604 else if ( filter instanceof GreaterEqNode )
605 {
606 GreaterEqNode node = ( ( GreaterEqNode ) filter );
607 Object value = node.getValue();
608
609 Value<?> newValue = convert( node.getAttribute(), value );
610
611 if ( newValue != null )
612 {
613 node.setValue( newValue );
614 }
615
616 }
617 else if ( filter instanceof LessEqNode )
618 {
619 LessEqNode node = ( ( LessEqNode ) filter );
620 Object value = node.getValue();
621
622 Value<?> newValue = convert( node.getAttribute(), value );
623
624 if ( newValue != null )
625 {
626 node.setValue( newValue );
627 }
628 }
629 else if ( filter instanceof ExtensibleNode )
630 {
631 ExtensibleNode node = ( ( ExtensibleNode ) filter );
632
633 if ( !schemaManager.lookupAttributeTypeRegistry( node.getAttribute() ).getSyntax().isHumanReadable() )
634 {
635 String message = I18n.err( I18n.ERR_51 );
636 LOG.error( message );
637 throw new LdapException( message );
638 }
639 }
640 else if ( filter instanceof ApproximateNode )
641 {
642 ApproximateNode node = ( ( ApproximateNode ) filter );
643 Object value = node.getValue();
644
645 Value<?> newValue = convert( node.getAttribute(), value );
646
647 if ( newValue != null )
648 {
649 node.setValue( newValue );
650 }
651 }
652 else if ( filter instanceof AssertionNode )
653 {
654 // Nothing to do
655 return;
656 }
657 else if ( filter instanceof ScopeNode )
658 {
659 // Nothing to do
660 return;
661 }
662 }
663 else
664 {
665 // Recursively iterate through all the children.
666 for ( ExprNode child : ( ( BranchNode ) filter ).getChildren() )
667 {
668 checkFilter( child );
669 }
670 }
671 }
672
673
674 public EntryFilteringCursor search( NextInterceptor nextInterceptor, SearchOperationContext opContext )
675 throws Exception
676 {
677 DN base = opContext.getDn();
678 SearchControls searchCtls = opContext.getSearchControls();
679 ExprNode filter = opContext.getFilter();
680
681 // We have to eliminate bad attributes from the request, accordingly
682 // to RFC 2251, chap. 4.5.1. Basically, all unknown attributes are removed
683 // from the list
684 if ( searchCtls.getReturningAttributes() != null )
685 {
686 filterAttributesToReturn( searchCtls );
687 }
688
689 // We also have to check the H/R flag for the filter attributes
690 checkFilter( filter );
691
692 String baseNormForm = ( base.isNormalized() ? base.getNormName() : base.getNormName() );
693
694 // Deal with the normal case : searching for a normal value (not subSchemaSubEntry)
695 if ( !subschemaSubentryDnNorm.equals( baseNormForm ) )
696 {
697 EntryFilteringCursor cursor = nextInterceptor.search( opContext );
698
699 if ( searchCtls.getReturningAttributes() != null )
700 {
701 cursor.addEntryFilter( topFilter );
702 return cursor;
703 }
704
705 for ( EntryFilter ef : filters )
706 {
707 cursor.addEntryFilter( ef );
708 }
709
710 return cursor;
711 }
712
713 // The user was searching into the subSchemaSubEntry
714 // This kind of search _must_ be limited to OBJECT scope (the subSchemaSubEntry
715 // does not have any sub level)
716 if ( searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE )
717 {
718 // The filter can be an equality or a presence, but nothing else
719 if ( filter instanceof SimpleNode )
720 {
721 // We should get the value for the filter.
722 // only 'top' and 'subSchema' are valid values
723 SimpleNode node = ( SimpleNode ) filter;
724 String objectClass;
725
726 objectClass = node.getValue().getString();
727
728 String objectClassOid = null;
729
730 if ( schemaManager.getObjectClassRegistry().contains( objectClass ) )
731 {
732 objectClassOid = schemaManager.getObjectClassRegistry().lookup( objectClass ).getOid();
733 }
734 else
735 {
736 return new BaseEntryFilteringCursor( new EmptyCursor<ServerEntry>(), opContext );
737 }
738
739 String nodeOid = schemaManager.getAttributeTypeRegistry().getOidByName( node.getAttribute() );
740
741 // see if node attribute is objectClass
742 if ( nodeOid.equals( SchemaConstants.OBJECT_CLASS_AT_OID )
743 && ( objectClassOid.equals( SchemaConstants.TOP_OC_OID ) || objectClassOid
744 .equals( SchemaConstants.SUBSCHEMA_OC_OID ) ) && ( node instanceof EqualityNode ) )
745 {
746 // call.setBypass( true );
747 ServerEntry serverEntry = schemaService.getSubschemaEntry( searchCtls.getReturningAttributes() );
748 serverEntry.setDn( base );
749 return new BaseEntryFilteringCursor( new SingletonCursor<ServerEntry>( serverEntry ), opContext );
750 }
751 else
752 {
753 return new BaseEntryFilteringCursor( new EmptyCursor<ServerEntry>(), opContext );
754 }
755 }
756 else if ( filter instanceof PresenceNode )
757 {
758 PresenceNode node = ( PresenceNode ) filter;
759
760 // see if node attribute is objectClass
761 if ( node.getAttribute().equals( SchemaConstants.OBJECT_CLASS_AT_OID ) )
762 {
763 // call.setBypass( true );
764 ServerEntry serverEntry = schemaService.getSubschemaEntry( searchCtls.getReturningAttributes() );
765 serverEntry.setDn( base );
766 EntryFilteringCursor cursor = new BaseEntryFilteringCursor( new SingletonCursor<ServerEntry>(
767 serverEntry ), opContext );
768 return cursor;
769 }
770 }
771 }
772
773 // In any case not handled previously, just return an empty result
774 return new BaseEntryFilteringCursor( new EmptyCursor<ServerEntry>(), opContext );
775 }
776
777
778 /**
779 * Search for an entry, using its DN. Binary attributes and ObjectClass attribute are removed.
780 */
781 public ClonedServerEntry lookup( NextInterceptor nextInterceptor, LookupOperationContext opContext )
782 throws Exception
783 {
784 ClonedServerEntry result = nextInterceptor.lookup( opContext );
785
786 if ( result == null )
787 {
788 return null;
789 }
790
791 filterBinaryAttributes( result );
792 filterObjectClass( result );
793
794 return result;
795 }
796
797
798 private void getSuperiors( ObjectClass oc, Set<String> ocSeen, List<ObjectClass> result ) throws Exception
799 {
800 for ( ObjectClass parent : oc.getSuperiors() )
801 {
802 // Skip 'top'
803 if ( SchemaConstants.TOP_OC.equals( parent.getName() ) )
804 {
805 continue;
806 }
807
808 if ( !ocSeen.contains( parent.getOid() ) )
809 {
810 ocSeen.add( parent.getOid() );
811 result.add( parent );
812 }
813
814 // Recurse on the parent
815 getSuperiors( parent, ocSeen, result );
816 }
817 }
818
819
820 /**
821 * Checks to see if an attribute is required by as determined from an entry's
822 * set of objectClass attribute values.
823 *
824 * @param attrId the attribute to test if required by a set of objectClass values
825 * @param objectClass the objectClass values
826 * @return true if the objectClass values require the attribute, false otherwise
827 * @throws Exception if the attribute is not recognized
828 */
829 private boolean isRequired( String attrId, EntryAttribute objectClasses ) throws Exception
830 {
831 OidRegistry oidRegistry = schemaManager.getGlobalOidRegistry();
832
833 if ( !oidRegistry.contains( attrId ) )
834 {
835 return false;
836 }
837
838 String attrOid = schemaManager.getAttributeTypeRegistry().getOidByName( attrId );
839
840 for ( Value<?> objectClass : objectClasses )
841 {
842 ObjectClass ocSpec = schemaManager.getObjectClassRegistry().lookup( objectClass.getString() );
843
844 for ( AttributeType must : ocSpec.getMustAttributeTypes() )
845 {
846 if ( must.getOid().equals( attrOid ) )
847 {
848 return true;
849 }
850 }
851 }
852
853 return false;
854 }
855
856
857 /**
858 * Checks to see if removing a set of attributes from an entry completely removes
859 * that attribute's values. If change has zero size then all attributes are
860 * presumed to be removed.
861 *
862 * @param change
863 * @param entry
864 * @return
865 * @throws Exception
866 */
867 private boolean isCompleteRemoval( EntryAttribute change, ServerEntry entry ) throws Exception
868 {
869 // if change size is 0 then all values are deleted then we're in trouble
870 if ( change.size() == 0 )
871 {
872 return true;
873 }
874
875 // can't do math to figure our if all values are removed since some
876 // values in the modify request may not be in the entry. we need to
877 // remove the values from a cloned version of the attribute and see
878 // if nothing is left.
879 EntryAttribute changedEntryAttr = entry.get( change.getUpId() ).clone();
880
881 for ( Value<?> value : change )
882 {
883 changedEntryAttr.remove( value );
884 }
885
886 return changedEntryAttr.size() == 0;
887 }
888
889
890 /**
891 *
892 * @param modOp
893 * @param changes
894 * @param existing
895 * @return
896 * @throws Exception
897 */
898 private EntryAttribute getResultantObjectClasses( ModificationOperation modOp, EntryAttribute changes,
899 EntryAttribute existing ) throws Exception
900 {
901 if ( ( changes == null ) && ( existing == null ) )
902 {
903 return new DefaultServerAttribute( SchemaConstants.OBJECT_CLASS_AT, OBJECT_CLASS );
904 }
905
906 if ( changes == null )
907 {
908 return existing;
909 }
910
911 if ( ( existing == null ) && ( modOp == ModificationOperation.ADD_ATTRIBUTE ) )
912 {
913 return changes;
914 }
915 else if ( existing == null )
916 {
917 return new DefaultServerAttribute( SchemaConstants.OBJECT_CLASS_AT, OBJECT_CLASS );
918 }
919
920 switch ( modOp )
921 {
922 case ADD_ATTRIBUTE:
923 for ( Value<?> value : changes )
924 {
925 existing.add( value );
926 }
927
928 return existing;
929
930 case REPLACE_ATTRIBUTE:
931 return changes.clone();
932
933 case REMOVE_ATTRIBUTE:
934 for ( Value<?> value : changes )
935 {
936 existing.remove( value );
937 }
938
939 return existing;
940
941 default:
942 throw new InternalError( "" );
943 }
944 }
945
946
947 private boolean getObjectClasses( EntryAttribute objectClasses, List<ObjectClass> result ) throws Exception
948 {
949 Set<String> ocSeen = new HashSet<String>();
950
951 // We must select all the ObjectClasses, except 'top',
952 // but including all the inherited ObjectClasses
953 boolean hasExtensibleObject = false;
954
955 for ( Value<?> objectClass : objectClasses )
956 {
957 String objectClassName = objectClass.getString();
958
959 if ( SchemaConstants.TOP_OC.equals( objectClassName ) )
960 {
961 continue;
962 }
963
964 if ( SchemaConstants.EXTENSIBLE_OBJECT_OC.equalsIgnoreCase( objectClassName ) )
965 {
966 hasExtensibleObject = true;
967 }
968
969 ObjectClass oc = schemaManager.getObjectClassRegistry().lookup( objectClassName );
970
971 // Add all unseen objectClasses to the list, except 'top'
972 if ( !ocSeen.contains( oc.getOid() ) )
973 {
974 ocSeen.add( oc.getOid() );
975 result.add( oc );
976 }
977
978 // Find all current OC parents
979 getSuperiors( oc, ocSeen, result );
980 }
981
982 return hasExtensibleObject;
983 }
984
985
986 private Set<String> getAllMust( EntryAttribute objectClasses ) throws Exception
987 {
988 Set<String> must = new HashSet<String>();
989
990 // Loop on all objectclasses
991 for ( Value<?> value : objectClasses )
992 {
993 String ocName = value.getString();
994 ObjectClass oc = schemaManager.getObjectClassRegistry().lookup( ocName );
995
996 List<AttributeType> types = oc.getMustAttributeTypes();
997
998 // For each objectClass, loop on all MUST attributeTypes, if any
999 if ( ( types != null ) && ( types.size() > 0 ) )
1000 {
1001 for ( AttributeType type : types )
1002 {
1003 must.add( type.getOid() );
1004 }
1005 }
1006 }
1007
1008 return must;
1009 }
1010
1011
1012 private Set<String> getAllAllowed( EntryAttribute objectClasses, Set<String> must ) throws Exception
1013 {
1014 Set<String> allowed = new HashSet<String>( must );
1015
1016 // Add the 'ObjectClass' attribute ID
1017 allowed.add( schemaManager.getAttributeTypeRegistry().getOidByName( SchemaConstants.OBJECT_CLASS_AT ) );
1018
1019 // Loop on all objectclasses
1020 for ( Value<?> objectClass : objectClasses )
1021 {
1022 String ocName = objectClass.getString();
1023 ObjectClass oc = schemaManager.getObjectClassRegistry().lookup( ocName );
1024
1025 List<AttributeType> types = oc.getMayAttributeTypes();
1026
1027 // For each objectClass, loop on all MAY attributeTypes, if any
1028 if ( ( types != null ) && ( types.size() > 0 ) )
1029 {
1030 for ( AttributeType type : types )
1031 {
1032 String oid = type.getOid();
1033
1034 allowed.add( oid );
1035 }
1036 }
1037 }
1038
1039 return allowed;
1040 }
1041
1042
1043 /**
1044 * Given the objectClasses for an entry, this method adds missing ancestors
1045 * in the hierarchy except for top which it removes. This is used for this
1046 * solution to DIREVE-276. More information about this solution can be found
1047 * <a href="http://docs.safehaus.org:8080/x/kBE">here</a>.
1048 *
1049 * @param objectClassAttr the objectClass attribute to modify
1050 * @throws Exception if there are problems
1051 */
1052 private void alterObjectClasses( EntryAttribute objectClassAttr ) throws Exception
1053 {
1054 Set<String> objectClasses = new HashSet<String>();
1055 Set<String> objectClassesUP = new HashSet<String>();
1056
1057 // Init the objectClass list with 'top'
1058 objectClasses.add( SchemaConstants.TOP_OC );
1059 objectClassesUP.add( SchemaConstants.TOP_OC );
1060
1061 // Construct the new list of ObjectClasses
1062 for ( Value<?> ocValue : objectClassAttr )
1063 {
1064 String ocName = ocValue.getString();
1065
1066 if ( !ocName.equalsIgnoreCase( SchemaConstants.TOP_OC ) )
1067 {
1068 String ocLowerName = ocName.toLowerCase();
1069
1070 ObjectClass objectClass = schemaManager.getObjectClassRegistry().lookup( ocLowerName );
1071
1072 if ( !objectClasses.contains( ocLowerName ) )
1073 {
1074 objectClasses.add( ocLowerName );
1075 objectClassesUP.add( ocName );
1076 }
1077
1078 List<ObjectClass> ocSuperiors = superiors.get( objectClass.getOid() );
1079
1080 if ( ocSuperiors != null )
1081 {
1082 for ( ObjectClass oc : ocSuperiors )
1083 {
1084 if ( !objectClasses.contains( oc.getName().toLowerCase() ) )
1085 {
1086 objectClasses.add( oc.getName() );
1087 objectClassesUP.add( oc.getName() );
1088 }
1089 }
1090 }
1091 }
1092 }
1093
1094 // Now, reset the ObjectClass attribute and put the new list into it
1095 objectClassAttr.clear();
1096
1097 for ( String attribute : objectClassesUP )
1098 {
1099 objectClassAttr.add( attribute );
1100 }
1101 }
1102
1103
1104 public void rename( NextInterceptor next, RenameOperationContext opContext ) throws Exception
1105 {
1106 DN oldDn = opContext.getDn();
1107 RDN newRdn = opContext.getNewRdn();
1108 boolean deleteOldRn = opContext.getDelOldDn();
1109 ServerEntry entry = (ServerEntry)opContext.getEntry().getClonedEntry();
1110
1111 /*
1112 * Note: This is only a consistency checks, to the ensure that all
1113 * mandatory attributes are available after deleting the old RDN.
1114 * The real modification is done in the XdbmStore class.
1115 * - TODO: this check is missing in the moveAndRename() method
1116 */
1117 if ( deleteOldRn )
1118 {
1119 ServerEntry tmpEntry = ( ServerEntry ) entry.clone();
1120 RDN oldRDN = oldDn.getRdn();
1121
1122 // Delete the old RDN means we remove some attributes and values.
1123 // We must make sure that after this operation all must attributes
1124 // are still present in the entry.
1125 for ( AVA atav : oldRDN )
1126 {
1127 AttributeType type = schemaManager.lookupAttributeTypeRegistry( atav.getUpType() );
1128 tmpEntry.remove( type, atav.getUpValue() );
1129 }
1130
1131 for ( AVA atav : newRdn )
1132 {
1133 AttributeType type = schemaManager.lookupAttributeTypeRegistry( atav.getUpType() );
1134
1135 if ( !tmpEntry.contains( type, atav.getNormValue() ) )
1136 {
1137 tmpEntry.add( new DefaultServerAttribute( type, atav.getUpValue() ) );
1138 }
1139 }
1140
1141 // Substitute the RDN and check if the new entry is correct
1142 tmpEntry.setDn( opContext.getNewDn() );
1143
1144 check( opContext.getNewDn(), tmpEntry );
1145
1146 // Check that no operational attributes are removed
1147 for ( AVA atav : oldRDN )
1148 {
1149 AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( atav.getUpType() );
1150
1151 if ( !attributeType.isUserModifiable() )
1152 {
1153 throw new LdapNoPermissionException( "Cannot modify the attribute '" + atav.getUpType() + "'" );
1154 }
1155 }
1156 }
1157
1158 next.rename( opContext );
1159 }
1160
1161
1162 /**
1163 * Create a new attribute using the given values
1164 */
1165 private EntryAttribute createNewAttribute( EntryAttribute attribute )
1166 {
1167 AttributeType attributeType = attribute.getAttributeType();
1168
1169 // Create the new Attribute
1170 EntryAttribute newAttribute = new DefaultServerAttribute( attribute.getUpId(), attributeType );
1171
1172 for ( Value<?> value : attribute )
1173 {
1174 newAttribute.add( value );
1175 }
1176
1177 return newAttribute;
1178 }
1179
1180
1181 /**
1182 * Modify an entry, applying the given modifications, and check if it's OK
1183 */
1184 private void checkModifyEntry( DN dn, ServerEntry currentEntry, List<Modification> mods ) throws Exception
1185 {
1186 // The first step is to check that the modifications are valid :
1187 // - the ATs are present in the schema
1188 // - The value is syntaxically correct
1189 //
1190 // While doing that, we will apply the modification to a copy of the current entry
1191 ServerEntry tempEntry = (ServerEntry)currentEntry.clone();
1192
1193 // Now, apply each mod one by one
1194 for ( Modification mod:mods )
1195 {
1196 EntryAttribute attribute = mod.getAttribute();
1197 AttributeType attributeType = attribute.getAttributeType();
1198
1199 // We don't allow modification of operational attributes
1200 if ( !attributeType.isUserModifiable() )
1201 {
1202 if ( !attributeType.equals( MODIFIERS_NAME_ATTRIBUTE_TYPE ) &&
1203 !attributeType.equals( MODIFY_TIMESTAMP_ATTRIBUTE_TYPE ) )
1204 {
1205 String msg = I18n.err( I18n.ERR_52, attributeType );
1206 LOG.error( msg );
1207 throw new LdapNoPermissionException( msg );
1208 }
1209 }
1210
1211 switch ( mod.getOperation() )
1212 {
1213 case ADD_ATTRIBUTE :
1214 // Check the syntax here
1215 if ( !attribute.isValid() )
1216 {
1217 // The value syntax is incorrect : this is an error
1218 String msg = I18n.err( I18n.ERR_53, attributeType );
1219 LOG.error( msg );
1220 throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX,
1221 msg );
1222 }
1223
1224 EntryAttribute currentAttribute = tempEntry.get( attributeType );
1225
1226 // First check if the added Attribute is already present in the entry
1227 // If not, we have to create the entry
1228 if ( currentAttribute != null )
1229 {
1230 for ( Value<?> value : attribute )
1231 {
1232 // At this point, we know that the attribute's syntax is correct
1233 // We just have to check that the current attribute does not
1234 // contains the value already
1235 if ( currentAttribute.contains( value ))
1236 {
1237 // This is an error.
1238 String msg = I18n.err( I18n.ERR_54, value );
1239 LOG.error( msg );
1240 throw new LdapAttributeInUseException( msg );
1241 }
1242
1243 currentAttribute.add( value );
1244 }
1245 }
1246 else
1247 {
1248 // We don't check if the attribute is not in the MUST or MAY at this
1249 // point, as one of the following modification can change the
1250 // ObjectClasses.
1251 EntryAttribute newAttribute = createNewAttribute( attribute );
1252
1253 tempEntry.put( newAttribute );
1254 }
1255
1256 break;
1257
1258 case REMOVE_ATTRIBUTE :
1259 // First check that the removed attribute exists
1260 if ( !tempEntry.containsAttribute( attributeType ) )
1261 {
1262 String msg = I18n.err( I18n.ERR_55, attributeType );
1263 LOG.error( msg );
1264 throw new LdapNoSuchAttributeException( msg );
1265 }
1266
1267 // We may have to remove the attribute or only some values
1268 if ( attribute.size() == 0 )
1269 {
1270 // No value : we have to remove the entire attribute
1271 tempEntry.removeAttributes( attributeType );
1272 }
1273 else
1274 {
1275 currentAttribute = tempEntry.get( attributeType );
1276
1277 // Now remove all the values
1278 for ( Value<?> value:attribute )
1279 {
1280 // We can only remove existing values.
1281 if ( currentAttribute.contains( value ) )
1282 {
1283 currentAttribute.remove( value );
1284 }
1285 else
1286 {
1287 String msg = I18n.err( I18n.ERR_56, attributeType );
1288 LOG.error( msg );
1289 throw new LdapNoSuchAttributeException( msg );
1290 }
1291 }
1292
1293
1294 // If the current attribute is empty, we have to remove
1295 // it from the entry
1296 if ( currentAttribute.size() == 0 )
1297 {
1298 tempEntry.removeAttributes( attributeType );
1299 }
1300 }
1301
1302 break;
1303
1304 case REPLACE_ATTRIBUTE :
1305 // The replaced attribute might not exist, it will then be a Add
1306 // If there is no value, then the attribute will be removed
1307 if ( !tempEntry.containsAttribute( attributeType ) )
1308 {
1309 if ( attribute.size() == 0 )
1310 {
1311 // Ignore the modification, as the attributeType does not
1312 // exists in the entry
1313 break;
1314 }
1315 else
1316 {
1317 // Create the new Attribute
1318 EntryAttribute newAttribute = createNewAttribute( attribute );
1319
1320 tempEntry.put( newAttribute );
1321 }
1322 }
1323 else
1324 {
1325 if ( attribute.size() == 0 )
1326 {
1327 // Remove the attribute from the entry
1328 tempEntry.removeAttributes( attributeType );
1329 }
1330 else
1331 {
1332 // Replace the existing values with the new values
1333 // This is done by removing the Attribute
1334 tempEntry.removeAttributes( attributeType );
1335
1336 // Create the new Attribute
1337 EntryAttribute newAttribute = createNewAttribute( attribute );
1338
1339 tempEntry.put( newAttribute );
1340 }
1341 }
1342
1343 break;
1344 }
1345 }
1346
1347 // Ok, we have created the modified entry. We now have to check that it's a valid
1348 // entry wrt the schema.
1349 // We have to check that :
1350 // - the rdn values are present in the entry
1351 // - the objectClasses inheritence is correct
1352 // - all the MUST are present
1353 // - all the attribute are in MUST and MAY, except fo the extensibleObeject OC
1354 // is present
1355 // - We haven't removed a part of the RDN
1356 check( dn, tempEntry );
1357 }
1358
1359
1360 /**
1361 * {@inheritDoc}
1362 */
1363 public void modify( NextInterceptor next, ModifyOperationContext opContext ) throws Exception
1364 {
1365 // A modification on a simple entry will be done in three steps :
1366 // - get the original entry (it should already been in the context)
1367 // - apply the modification on it
1368 // - check that the entry is still correct
1369 // - add the operational attributes (modifiersName/modifyTimeStamp)
1370 // - store the modified entry on the backend.
1371 //
1372 // A modification done on the schema is a bit different, as there is two more
1373 // steps
1374 // - We have to update the registries
1375 // - We have to modify the ou=schemaModifications entry
1376 //
1377
1378 // First, check that the entry is either a subschemaSubentry or a schema element.
1379 // This is the case if it's a child of cn=schema or ou=schema
1380 DN dn = opContext.getDn();
1381
1382 // Gets the stored entry on which the modification must be applied
1383 if ( dn.equals( subschemaSubentryDn ) )
1384 {
1385 LOG.debug( "Modification attempt on schema subentry {}: \n{}", dn, opContext );
1386
1387 // We can get rid of the modifiersName and modifyTimestamp, they are useless.
1388 List<Modification> mods = opContext.getModItems();
1389 List<Modification> cleanMods = new ArrayList<Modification>();
1390
1391 for ( Modification mod:mods )
1392 {
1393 AttributeType at = ( (ServerModification)mod).getAttribute().getAttributeType();
1394
1395 if ( !MODIFIERS_NAME_ATTRIBUTE_TYPE.equals( at ) && !MODIFY_TIMESTAMP_ATTRIBUTE_TYPE.equals( at ) )
1396 {
1397 cleanMods.add( mod );
1398 }
1399 }
1400
1401 opContext.setModItems( cleanMods );
1402
1403 // Now that the entry has been modified, update the SSSE
1404 schemaSubEntryManager.modifySchemaSubentry( opContext, opContext.hasRequestControl( CascadeControl.CONTROL_OID ) );
1405
1406 return;
1407 }
1408
1409 ServerEntry entry = opContext.getEntry();
1410 List<Modification> modifications = opContext.getModItems();
1411 checkModifyEntry( dn, entry, modifications );
1412
1413 next.modify( opContext );
1414 }
1415
1416
1417 /**
1418 * Filter the attributes by removing the ones which are not allowed
1419 */
1420 private void filterAttributeTypes( SearchingOperationContext operation, ClonedServerEntry result )
1421 {
1422 if ( operation.getReturningAttributes() == null )
1423 {
1424 return;
1425 }
1426
1427 for ( AttributeTypeOptions attrOptions : operation.getReturningAttributes() )
1428 {
1429 EntryAttribute attribute = result.get( attrOptions.getAttributeType() );
1430
1431 if ( attrOptions.hasOption() )
1432 {
1433 for ( String option : attrOptions.getOptions() )
1434 {
1435 if ( "binary".equalsIgnoreCase( option ) )
1436 {
1437 continue;
1438 }
1439 else
1440 {
1441 try
1442 {
1443 if ( result.contains( attribute ) )
1444 {
1445 result.remove( attribute );
1446 }
1447 }
1448 catch ( LdapException ne )
1449 {
1450 // Do nothings
1451 }
1452 break;
1453 }
1454 }
1455
1456 }
1457 }
1458 }
1459
1460
1461 private void filterObjectClass( ServerEntry entry ) throws Exception
1462 {
1463 List<ObjectClass> objectClasses = new ArrayList<ObjectClass>();
1464 EntryAttribute oc = entry.get( SchemaConstants.OBJECT_CLASS_AT );
1465
1466 if ( oc != null )
1467 {
1468 getObjectClasses( oc, objectClasses );
1469
1470 entry.removeAttributes( SchemaConstants.OBJECT_CLASS_AT );
1471
1472 EntryAttribute newOc = new DefaultServerAttribute( oc.getAttributeType() );
1473
1474 for ( ObjectClass currentOC : objectClasses )
1475 {
1476 newOc.add( currentOC.getName() );
1477 }
1478
1479 newOc.add( SchemaConstants.TOP_OC );
1480 entry.put( newOc );
1481 }
1482 }
1483
1484
1485 private void filterBinaryAttributes( ServerEntry entry ) throws Exception
1486 {
1487 /*
1488 * start converting values of attributes to byte[]s which are not
1489 * human readable and those that are in the binaries set
1490 */
1491 for ( EntryAttribute attribute : entry )
1492 {
1493 if ( !attribute.getAttributeType().getSyntax().isHumanReadable() )
1494 {
1495 List<Value<?>> binaries = new ArrayList<Value<?>>();
1496
1497 for ( Value<?> value : attribute )
1498 {
1499 binaries.add( new BinaryValue( attribute.getAttributeType(),
1500 value.getBytes() ) );
1501 }
1502
1503 attribute.clear();
1504 attribute.put( binaries );
1505 }
1506 }
1507 }
1508
1509 /**
1510 * A special filter over entry attributes which replaces Attribute String values with their respective byte[]
1511 * representations using schema information and the value held in the JNDI environment property:
1512 * <code>java.naming.ldap.attributes.binary</code>.
1513 *
1514 * @see <a href= "http://java.sun.com/j2se/1.4.2/docs/guide/jndi/jndi-ldap-gl.html#binary">
1515 * java.naming.ldap.attributes.binary</a>
1516 */
1517 private class BinaryAttributeFilter implements EntryFilter
1518 {
1519 public boolean accept( SearchingOperationContext operation, ClonedServerEntry result ) throws Exception
1520 {
1521 filterBinaryAttributes( result );
1522 return true;
1523 }
1524 }
1525
1526 /**
1527 * Filters objectClass attribute to inject top when not present.
1528 */
1529 private class TopFilter implements EntryFilter
1530 {
1531 public boolean accept( SearchingOperationContext operation, ClonedServerEntry result ) throws Exception
1532 {
1533 filterObjectClass( result );
1534 filterAttributeTypes( operation, result );
1535 return true;
1536 }
1537 }
1538
1539
1540 /**
1541 * Check that all the attributes exist in the schema for this entry.
1542 *
1543 * We also check the syntaxes
1544 */
1545 private void check( DN dn, ServerEntry entry ) throws Exception
1546 {
1547 // ---------------------------------------------------------------
1548 // First, make sure all attributes are valid schema defined attributes
1549 // ---------------------------------------------------------------
1550
1551 for ( AttributeType attributeType : entry.getAttributeTypes() )
1552 {
1553 if ( !schemaManager.getAttributeTypeRegistry().contains( attributeType.getName() ) )
1554 {
1555 throw new LdapInvalidAttributeTypeException( I18n.err( I18n.ERR_275,
1556 attributeType.getName() ) );
1557 }
1558 }
1559
1560 // We will check some elements :
1561 // 1) the entry must have all the MUST attributes of all its ObjectClass
1562 // 2) The SingleValued attributes must be SingleValued
1563 // 3) No attributes should be used if they are not part of MUST and MAY
1564 // 3-1) Except if the extensibleObject ObjectClass is used
1565 // 3-2) or if the AttributeType is COLLECTIVE
1566 // 4) We also check that for H-R attributes, we have a valid String in the values
1567 EntryAttribute objectClassAttr = entry.get( SchemaConstants.OBJECT_CLASS_AT );
1568
1569 // Protect the server against a null objectClassAttr
1570 // It can be the case if the user forgot to add it to the entry ...
1571 // In this case, we create an new one, empty
1572 if ( objectClassAttr == null )
1573 {
1574 objectClassAttr = new DefaultServerAttribute( SchemaConstants.OBJECT_CLASS_AT, OBJECT_CLASS );
1575 }
1576
1577 List<ObjectClass> ocs = new ArrayList<ObjectClass>();
1578
1579 alterObjectClasses( objectClassAttr );
1580
1581 // Now we can process the MUST and MAY attributes
1582 Set<String> must = getAllMust( objectClassAttr );
1583 Set<String> allowed = getAllAllowed( objectClassAttr, must );
1584
1585 boolean hasExtensibleObject = getObjectClasses( objectClassAttr, ocs );
1586
1587 // As we now have all the ObjectClasses updated, we have
1588 // to check that we don't have conflicting ObjectClasses
1589 assertObjectClasses( dn, ocs );
1590
1591 assertRequiredAttributesPresent( dn, entry, must );
1592 assertNumberOfAttributeValuesValid( entry );
1593
1594 if ( !hasExtensibleObject )
1595 {
1596 assertAllAttributesAllowed( dn, entry, allowed );
1597 }
1598
1599 // Check the attributes values and transform them to String if necessary
1600 assertHumanReadable( entry );
1601
1602 // Now check the syntaxes
1603 assertSyntaxes( entry );
1604
1605 assertRdn ( dn, entry );
1606 }
1607
1608
1609 private void checkOcSuperior( ServerEntry entry ) throws Exception
1610 {
1611 // handle the m-supObjectClass meta attribute
1612 EntryAttribute supOC = entry.get( MetaSchemaConstants.M_SUP_OBJECT_CLASS_AT );
1613
1614 if ( supOC != null )
1615 {
1616 ObjectClassTypeEnum ocType = ObjectClassTypeEnum.STRUCTURAL;
1617
1618 if ( entry.get( MetaSchemaConstants.M_TYPE_OBJECT_CLASS_AT ) != null )
1619 {
1620 String type = entry.get( MetaSchemaConstants.M_TYPE_OBJECT_CLASS_AT ).getString();
1621 ocType = ObjectClassTypeEnum.getClassType( type );
1622 }
1623
1624 // First check that the inheritence scheme is correct.
1625 // 1) If the ocType is ABSTRACT, it should not have any other SUP not ABSTRACT
1626 for ( Value<?> sup:supOC )
1627 {
1628 try
1629 {
1630 String supName = sup.getString();
1631
1632 ObjectClass superior = schemaManager.getObjectClassRegistry().lookup( supName );
1633
1634 switch ( ocType )
1635 {
1636 case ABSTRACT :
1637 if ( !superior.isAbstract() )
1638 {
1639 String message = I18n.err( I18n.ERR_57 );
1640 LOG.error( message );
1641 throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, message );
1642 }
1643
1644 break;
1645
1646 case AUXILIARY :
1647 if ( !superior.isAbstract() && ! superior.isAuxiliary() )
1648 {
1649 String message = I18n.err( I18n.ERR_58 );
1650 LOG.error( message );
1651 throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, message );
1652 }
1653
1654 break;
1655
1656 case STRUCTURAL :
1657 break;
1658 }
1659 }
1660 catch ( LdapException ne )
1661 {
1662 // The superior OC does not exist : this is an error
1663 String message = I18n.err( I18n.ERR_59 );
1664 LOG.error( message );
1665 throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, message );
1666 }
1667 }
1668 }
1669 }
1670
1671
1672 /**
1673 * Check that all the attributes exist in the schema for this entry.
1674 */
1675 public void add( NextInterceptor next, AddOperationContext addContext ) throws Exception
1676 {
1677 DN name = addContext.getDn();
1678 ServerEntry entry = addContext.getEntry();
1679
1680 check( name, entry );
1681
1682 // Special checks for the MetaSchema branch
1683 if ( name.isChildOf( schemaBaseDN ) )
1684 {
1685 // get the schema name
1686 String schemaName = getSchemaName( name );
1687
1688 if ( entry.contains( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.META_SCHEMA_OC ) )
1689 {
1690 next.add( addContext );
1691
1692 if ( schemaManager.isSchemaLoaded( schemaName ) )
1693 {
1694 // Update the OC superiors for each added ObjectClass
1695 computeSuperiors();
1696 }
1697 }
1698 else if ( entry.contains( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.META_OBJECT_CLASS_OC ) )
1699 {
1700 // This is an ObjectClass addition
1701 checkOcSuperior( addContext.getEntry() );
1702
1703 next.add( addContext );
1704
1705 // Update the structures now that the schema element has been added
1706 Schema schema = schemaManager.getLoadedSchema( schemaName );
1707
1708 if ( ( schema != null ) && schema.isEnabled() )
1709 {
1710 String ocName = entry.get( MetaSchemaConstants.M_NAME_AT ).getString();
1711 ObjectClass addedOC = schemaManager.getObjectClassRegistry().lookup( ocName );
1712 computeSuperior( addedOC );
1713 }
1714 }
1715 else if ( entry.contains( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.META_ATTRIBUTE_TYPE_OC ) )
1716 {
1717
1718 // This is an AttributeType addition
1719 next.add( addContext );
1720 }
1721 else
1722 {
1723 next.add( addContext );
1724 }
1725
1726 }
1727 else
1728 {
1729 next.add( addContext );
1730 }
1731 }
1732
1733
1734 private String getSchemaName( DN dn ) throws LdapException
1735 {
1736 if ( dn.size() < 2 )
1737 {
1738 throw new LdapException( I18n.err( I18n.ERR_276 ) );
1739 }
1740
1741 RDN rdn = dn.getRdn( 1 );
1742 return ( String ) rdn.getNormValue();
1743 }
1744
1745
1746 /**
1747 * Checks to see if an attribute is required by as determined from an entry's
1748 * set of objectClass attribute values.
1749 *
1750 * @return true if the objectClass values require the attribute, false otherwise
1751 * @throws Exception if the attribute is not recognized
1752 */
1753 private void assertAllAttributesAllowed( DN dn, ServerEntry entry, Set<String> allowed ) throws Exception
1754 {
1755 // Never check the attributes if the extensibleObject objectClass is
1756 // declared for this entry
1757 EntryAttribute objectClass = entry.get( SchemaConstants.OBJECT_CLASS_AT );
1758
1759 if ( objectClass.contains( SchemaConstants.EXTENSIBLE_OBJECT_OC ) )
1760 {
1761 return;
1762 }
1763
1764 for ( EntryAttribute attribute : entry )
1765 {
1766 String attrOid = attribute.getAttributeType().getOid();
1767
1768 AttributeType attributeType = attribute.getAttributeType();
1769
1770 if ( !attributeType.isCollective() && ( attributeType.getUsage() == UsageEnum.USER_APPLICATIONS ) )
1771 {
1772 if ( !allowed.contains( attrOid ) )
1773 {
1774 throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, I18n.err( I18n.ERR_277, attribute.getUpId(),
1775 dn.getName() ) );
1776 }
1777 }
1778 }
1779 }
1780
1781
1782 /**
1783 * Checks to see number of values of an attribute conforms to the schema
1784 */
1785 private void assertNumberOfAttributeValuesValid( Entry entry ) throws LdapInvalidAttributeValueException
1786 {
1787 for ( EntryAttribute attribute : entry )
1788 {
1789 assertNumberOfAttributeValuesValid( attribute );
1790 }
1791 }
1792
1793
1794 /**
1795 * Checks to see numbers of values of attributes conforms to the schema
1796 */
1797 private void assertNumberOfAttributeValuesValid( EntryAttribute attribute ) throws LdapInvalidAttributeValueException
1798 {
1799 if ( attribute.size() > 1 && attribute.getAttributeType().isSingleValued() )
1800 {
1801 throw new LdapInvalidAttributeValueException( ResultCodeEnum.CONSTRAINT_VIOLATION,
1802 I18n.err( I18n.ERR_278, attribute.getUpId() ) );
1803 }
1804 }
1805
1806
1807 /**
1808 * Checks to see the presence of all required attributes within an entry.
1809 */
1810 private void assertRequiredAttributesPresent( DN dn, Entry entry, Set<String> must ) throws Exception
1811 {
1812 for ( EntryAttribute attribute : entry )
1813 {
1814 must.remove( attribute.getAttributeType().getOid() );
1815 }
1816
1817 if ( must.size() != 0 )
1818 {
1819 throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION,
1820 I18n.err( I18n.ERR_279, must, dn.getName() ) );
1821 }
1822 }
1823
1824
1825 /**
1826 * Checck that OC does not conflict :
1827 * - we can't have more than one STRUCTURAL OC unless they are in the same
1828 * inheritance tree
1829 * - we must have at least one STRUCTURAL OC
1830 */
1831 private void assertObjectClasses( DN dn, List<ObjectClass> ocs ) throws Exception
1832 {
1833 Set<ObjectClass> structuralObjectClasses = new HashSet<ObjectClass>();
1834
1835 /*
1836 * Since the number of ocs present in an entry is small it's not
1837 * so expensive to take two passes while determining correctness
1838 * since it will result in clear simple code instead of a deep nasty
1839 * for loop with nested loops. Plus after the first pass we can
1840 * quickly know if there are no structural object classes at all.
1841 */
1842
1843 // --------------------------------------------------------------------
1844 // Extract all structural objectClasses within the entry
1845 // --------------------------------------------------------------------
1846 for ( ObjectClass oc : ocs )
1847 {
1848 if ( oc.isStructural() )
1849 {
1850 structuralObjectClasses.add( oc );
1851 }
1852 }
1853
1854 // --------------------------------------------------------------------
1855 // Throw an error if no STRUCTURAL objectClass are found.
1856 // --------------------------------------------------------------------
1857
1858 if ( structuralObjectClasses.isEmpty() )
1859 {
1860 String message = I18n.err( I18n.ERR_60, dn );
1861 LOG.error( message );
1862 throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, message );
1863 }
1864
1865 // --------------------------------------------------------------------
1866 // Put all structural object classes into new remaining container and
1867 // start removing any which are superiors of others in the set. What
1868 // is left in the remaining set will be unrelated structural
1869 /// objectClasses. If there is more than one then we have a problem.
1870 // --------------------------------------------------------------------
1871
1872 Set<ObjectClass> remaining = new HashSet<ObjectClass>( structuralObjectClasses.size() );
1873 remaining.addAll( structuralObjectClasses );
1874
1875 for ( ObjectClass oc : structuralObjectClasses )
1876 {
1877 if ( oc.getSuperiors() != null )
1878 {
1879 for ( ObjectClass superClass : oc.getSuperiors() )
1880 {
1881 if ( superClass.isStructural() )
1882 {
1883 remaining.remove( superClass );
1884 }
1885 }
1886 }
1887 }
1888
1889 // Like the highlander there can only be one :).
1890 if ( remaining.size() > 1 )
1891 {
1892 String message = I18n.err( I18n.ERR_61, dn, remaining );
1893 LOG.error( message );
1894 throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, message );
1895 }
1896 }
1897
1898
1899 /**
1900 * Check the entry attributes syntax, using the syntaxCheckers
1901 */
1902 private void assertSyntaxes( Entry entry ) throws Exception
1903 {
1904 // First, loop on all attributes
1905 for ( EntryAttribute attribute : entry )
1906 {
1907 AttributeType attributeType = attribute.getAttributeType();
1908 SyntaxChecker syntaxChecker = attributeType.getSyntax().getSyntaxChecker();
1909
1910 if ( syntaxChecker instanceof OctetStringSyntaxChecker )
1911 {
1912 // This is a speedup : no need to check the syntax of any value
1913 // if all the syntaxes are accepted...
1914 continue;
1915 }
1916
1917 // Then loop on all values
1918 for ( Value<?> value : attribute )
1919 {
1920 if ( value.isValid() )
1921 {
1922 // No need to validate something which is already ok
1923 continue;
1924 }
1925
1926 try
1927 {
1928 syntaxChecker.assertSyntax( value.get() );
1929 }
1930 catch ( Exception ne )
1931 {
1932 String message = I18n.err( I18n.ERR_280, value.getString(), attribute.getUpId() );
1933 LOG.info( message );
1934
1935 throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, message );
1936 }
1937 }
1938 }
1939 }
1940
1941
1942 private void assertRdn( DN dn, ServerEntry entry ) throws Exception
1943 {
1944 for ( AVA atav : dn.getRdn() )
1945 {
1946 EntryAttribute attribute = entry.get( atav.getNormType() );
1947
1948 if ( ( attribute == null ) || ( !attribute.contains( atav.getNormValue() ) ) )
1949 {
1950 String message = I18n.err( I18n.ERR_62, dn, atav.getUpType() );
1951 LOG.error( message );
1952 throw new LdapSchemaViolationException( ResultCodeEnum.NOT_ALLOWED_ON_RDN, message );
1953 }
1954 }
1955 }
1956
1957
1958 /**
1959 * Check a String attribute to see if there is some byte[] value in it.
1960 *
1961 * If this is the case, try to change it to a String value.
1962 */
1963 private boolean checkHumanReadable( EntryAttribute attribute ) throws Exception
1964 {
1965 boolean isModified = false;
1966
1967 // Loop on each values
1968 for ( Value<?> value : attribute )
1969 {
1970 if ( value instanceof StringValue )
1971 {
1972 continue;
1973 }
1974 else if ( value instanceof BinaryValue )
1975 {
1976 // we have a byte[] value. It should be a String UTF-8 encoded
1977 // Let's transform it
1978 try
1979 {
1980 String valStr = new String( value.getBytes(), "UTF-8" );
1981 attribute.remove( value );
1982 attribute.add( valStr );
1983 isModified = true;
1984 }
1985 catch ( UnsupportedEncodingException uee )
1986 {
1987 throw new LdapException( I18n.err( I18n.ERR_281 ) );
1988 }
1989 }
1990 else
1991 {
1992 throw new LdapException( I18n.err( I18n.ERR_282 ) );
1993 }
1994 }
1995
1996 return isModified;
1997 }
1998
1999
2000 /**
2001 * Check a binary attribute to see if there is some String value in it.
2002 *
2003 * If this is the case, try to change it to a binary value.
2004 */
2005 private boolean checkNotHumanReadable( EntryAttribute attribute ) throws Exception
2006 {
2007 boolean isModified = false;
2008
2009 // Loop on each values
2010 for ( Value<?> value : attribute )
2011 {
2012 if ( value instanceof BinaryValue )
2013 {
2014 continue;
2015 }
2016 else if ( value instanceof StringValue )
2017 {
2018 // We have a String value. It should be a byte[]
2019 // Let's transform it
2020 try
2021 {
2022 byte[] valBytes = value.getString().getBytes( "UTF-8" );
2023
2024 attribute.remove( value );
2025 attribute.add( valBytes );
2026 isModified = true;
2027 }
2028 catch ( UnsupportedEncodingException uee )
2029 {
2030 String message = I18n.err( I18n.ERR_63 );
2031 LOG.error( message );
2032 throw new LdapException( message );
2033 }
2034 }
2035 else
2036 {
2037 String message = I18n.err( I18n.ERR_64 );
2038 LOG.error( message );
2039 throw new LdapException( message );
2040 }
2041 }
2042
2043 return isModified;
2044 }
2045
2046
2047 /**
2048 * Check that all the attribute's values which are Human Readable can be transformed
2049 * to valid String if they are stored as byte[], and that non Human Readable attributes
2050 * stored as String can be transformed to byte[]
2051 */
2052 private void assertHumanReadable( ServerEntry entry ) throws Exception
2053 {
2054 boolean isModified = false;
2055
2056 ServerEntry clonedEntry = null;
2057
2058 // Loops on all attributes
2059 for ( EntryAttribute attribute : entry )
2060 {
2061 AttributeType attributeType = attribute.getAttributeType();
2062
2063 // If the attributeType is H-R, check all of its values
2064 if ( attributeType.getSyntax().isHumanReadable() )
2065 {
2066 isModified = checkHumanReadable( attribute );
2067 }
2068 else
2069 {
2070 isModified = checkNotHumanReadable( attribute );
2071 }
2072
2073 // If we have a returned attribute, then we need to store it
2074 // into a new entry
2075 if ( isModified )
2076 {
2077 if ( clonedEntry == null )
2078 {
2079 clonedEntry = ( ServerEntry ) entry.clone();
2080 }
2081
2082 // Switch the attributes
2083 clonedEntry.put( attribute );
2084
2085 isModified = false;
2086 }
2087 }
2088
2089 if ( clonedEntry != null )
2090 {
2091 entry = clonedEntry;
2092 }
2093 }
2094 }