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