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