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 01:59:49 +0200 (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 }