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.normalization; 021 022 023import org.apache.directory.api.ldap.model.constants.SchemaConstants; 024import org.apache.directory.api.ldap.model.cursor.EmptyCursor; 025import org.apache.directory.api.ldap.model.entry.Entry; 026import org.apache.directory.api.ldap.model.entry.Modification; 027import org.apache.directory.api.ldap.model.entry.Value; 028import org.apache.directory.api.ldap.model.exception.LdapException; 029import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeTypeException; 030import org.apache.directory.api.ldap.model.filter.AndNode; 031import org.apache.directory.api.ldap.model.filter.BranchNode; 032import org.apache.directory.api.ldap.model.filter.EqualityNode; 033import org.apache.directory.api.ldap.model.filter.ExprNode; 034import org.apache.directory.api.ldap.model.filter.LeafNode; 035import org.apache.directory.api.ldap.model.filter.NotNode; 036import org.apache.directory.api.ldap.model.filter.ObjectClassNode; 037import org.apache.directory.api.ldap.model.filter.OrNode; 038import org.apache.directory.api.ldap.model.filter.PresenceNode; 039import org.apache.directory.api.ldap.model.filter.UndefinedNode; 040import org.apache.directory.api.ldap.model.name.Ava; 041import org.apache.directory.api.ldap.model.name.Dn; 042import org.apache.directory.api.ldap.model.name.Rdn; 043import org.apache.directory.api.ldap.model.schema.AttributeType; 044import org.apache.directory.api.ldap.model.schema.normalizers.ConcreteNameComponentNormalizer; 045import org.apache.directory.api.ldap.model.schema.normalizers.NameComponentNormalizer; 046import org.apache.directory.server.core.api.DirectoryService; 047import org.apache.directory.server.core.api.InterceptorEnum; 048import org.apache.directory.server.core.api.filtering.EntryFilteringCursorImpl; 049import org.apache.directory.server.core.api.filtering.EntryFilteringCursor; 050import org.apache.directory.server.core.api.interceptor.BaseInterceptor; 051import org.apache.directory.server.core.api.interceptor.context.AddOperationContext; 052import org.apache.directory.server.core.api.interceptor.context.CompareOperationContext; 053import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext; 054import org.apache.directory.server.core.api.interceptor.context.HasEntryOperationContext; 055import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext; 056import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext; 057import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext; 058import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext; 059import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext; 060import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext; 061import org.apache.directory.server.core.api.normalization.FilterNormalizingVisitor; 062import org.apache.directory.server.i18n.I18n; 063import org.slf4j.Logger; 064import org.slf4j.LoggerFactory; 065 066 067/** 068 * A name normalization service. This service makes sure all relative and distinguished 069 * names are normalized before calls are made against the respective interface methods 070 * on DefaultPartitionNexus. 071 * 072 * The Filters are also normalized. 073 * 074 * If the Rdn AttributeTypes are not present in the entry for an Add request, 075 * they will be added. 076 * 077 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 078 */ 079public class NormalizationInterceptor extends BaseInterceptor 080{ 081 /** logger used by this class */ 082 private static final Logger LOG = LoggerFactory.getLogger( NormalizationInterceptor.class ); 083 084 /** a filter node value normalizer and undefined node remover */ 085 private FilterNormalizingVisitor normVisitor; 086 087 088 /** 089 * Creates a new instance of a NormalizationInterceptor. 090 */ 091 public NormalizationInterceptor() 092 { 093 super( InterceptorEnum.NORMALIZATION_INTERCEPTOR ); 094 } 095 096 097 /** 098 * Initialize the registries, normalizers. 099 */ 100 @Override 101 public void init( DirectoryService directoryService ) throws LdapException 102 { 103 LOG.debug( "Initialiazing the NormalizationInterceptor" ); 104 105 super.init( directoryService ); 106 107 NameComponentNormalizer ncn = new ConcreteNameComponentNormalizer( schemaManager ); 108 normVisitor = new FilterNormalizingVisitor( ncn, schemaManager ); 109 } 110 111 112 /** 113 * The destroy method does nothing 114 */ 115 @Override 116 public void destroy() 117 { 118 } 119 120 121 // ------------------------------------------------------------------------ 122 // Normalize all Name based arguments for ContextPartition interface operations 123 // ------------------------------------------------------------------------ 124 /** 125 * {@inheritDoc} 126 */ 127 @Override 128 public void add( AddOperationContext addContext ) throws LdapException 129 { 130 Dn addDn = addContext.getDn(); 131 132 if ( !addDn.isSchemaAware() ) 133 { 134 addContext.setDn( new Dn( schemaManager, addDn ) ); 135 } 136 137 Dn entryDn = addContext.getEntry().getDn(); 138 139 if ( !entryDn.isSchemaAware() ) 140 { 141 addContext.getEntry().setDn( new Dn( schemaManager, entryDn ) ); 142 } 143 144 addRdnAttributesToEntry( addContext.getDn(), addContext.getEntry() ); 145 146 next( addContext ); 147 } 148 149 150 /** 151 * {@inheritDoc} 152 */ 153 @Override 154 public boolean compare( CompareOperationContext compareContext ) throws LdapException 155 { 156 Dn dn = compareContext.getDn(); 157 158 if ( !dn.isSchemaAware() ) 159 { 160 compareContext.setDn( new Dn( schemaManager, dn ) ); 161 } 162 163 // Get the attributeType from the OID 164 try 165 { 166 AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( compareContext.getOid() ); 167 168 // Translate the value from binary to String if the AT is HR 169 if ( attributeType.getSyntax().isHumanReadable() && ( !compareContext.getValue().isHumanReadable() ) ) 170 { 171 compareContext.setValue( compareContext.getValue() ); 172 } 173 174 compareContext.setAttributeType( attributeType ); 175 } 176 catch ( LdapException le ) 177 { 178 throw new LdapInvalidAttributeTypeException( I18n.err( I18n.ERR_266, compareContext.getOid() ) ); 179 } 180 181 return next( compareContext ); 182 } 183 184 185 /** 186 * {@inheritDoc} 187 */ 188 @Override 189 public void delete( DeleteOperationContext deleteContext ) throws LdapException 190 { 191 Dn dn = deleteContext.getDn(); 192 193 if ( !dn.isSchemaAware() ) 194 { 195 deleteContext.setDn( new Dn( schemaManager, dn ) ); 196 } 197 198 next( deleteContext ); 199 } 200 201 202 /** 203 * {@inheritDoc} 204 */ 205 @Override 206 public boolean hasEntry( HasEntryOperationContext hasEntryContext ) throws LdapException 207 { 208 Dn dn = hasEntryContext.getDn(); 209 210 if ( !dn.isSchemaAware() ) 211 { 212 hasEntryContext.setDn( new Dn( schemaManager, dn ) ); 213 } 214 215 return next( hasEntryContext ); 216 } 217 218 219 /** 220 * {@inheritDoc} 221 */ 222 @Override 223 public Entry lookup( LookupOperationContext lookupContext ) throws LdapException 224 { 225 Dn dn = lookupContext.getDn(); 226 227 if ( !dn.isSchemaAware() ) 228 { 229 lookupContext.setDn( new Dn( schemaManager, dn ) ); 230 } 231 232 return next( lookupContext ); 233 } 234 235 236 /** 237 * {@inheritDoc} 238 */ 239 @Override 240 public void modify( ModifyOperationContext modifyContext ) throws LdapException 241 { 242 Dn dn = modifyContext.getDn(); 243 244 if ( !dn.isSchemaAware() ) 245 { 246 modifyContext.setDn( new Dn( schemaManager, dn ) ); 247 } 248 249 if ( modifyContext.getModItems() != null ) 250 { 251 for ( Modification modification : modifyContext.getModItems() ) 252 { 253 AttributeType attributeType = schemaManager.getAttributeType( modification.getAttribute().getId() ); 254 modification.apply( attributeType ); 255 } 256 } 257 258 next( modifyContext ); 259 } 260 261 262 /** 263 * {@inheritDoc} 264 */ 265 @Override 266 public void move( MoveOperationContext moveContext ) throws LdapException 267 { 268 Dn moveDn = moveContext.getDn(); 269 270 if ( !moveDn.isSchemaAware() ) 271 { 272 moveContext.setDn( new Dn( schemaManager, moveDn ) ); 273 } 274 275 Dn oldSuperiorDn = moveContext.getOldSuperior(); 276 277 if ( !oldSuperiorDn.isSchemaAware() ) 278 { 279 moveContext.setOldSuperior( new Dn( schemaManager, oldSuperiorDn ) ); 280 } 281 282 Dn newSuperiorDn = moveContext.getNewSuperior(); 283 284 if ( !newSuperiorDn.isSchemaAware() ) 285 { 286 moveContext.setNewSuperior( new Dn( schemaManager, newSuperiorDn ) ); 287 } 288 289 Dn newDn = moveContext.getNewDn(); 290 291 if ( !newDn.isSchemaAware() ) 292 { 293 moveContext.setNewDn( new Dn( schemaManager, newDn ) ); 294 } 295 296 Rdn rdn = moveContext.getRdn(); 297 298 if ( !rdn.isSchemaAware() ) 299 { 300 moveContext.setRdn( new Rdn( schemaManager, rdn ) ); 301 } 302 303 next( moveContext ); 304 } 305 306 307 /** 308 * {@inheritDoc} 309 */ 310 @Override 311 public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException 312 { 313 Rdn newRdn = moveAndRenameContext.getNewRdn(); 314 315 if ( !newRdn.isSchemaAware() ) 316 { 317 moveAndRenameContext.setNewRdn( new Rdn( schemaManager, newRdn ) ); 318 } 319 320 Dn dn = moveAndRenameContext.getDn(); 321 322 if ( !dn.isSchemaAware() ) 323 { 324 moveAndRenameContext.setDn( new Dn( schemaManager, dn ) ); 325 } 326 327 Dn newDn = moveAndRenameContext.getNewDn(); 328 329 if ( !newDn.isSchemaAware() ) 330 { 331 moveAndRenameContext.setNewDn( new Dn( schemaManager, newDn ) ); 332 } 333 334 Dn newSuperiorDn = moveAndRenameContext.getNewSuperiorDn(); 335 336 if ( !newSuperiorDn.isSchemaAware() ) 337 { 338 moveAndRenameContext.setNewSuperiorDn( new Dn( schemaManager, newSuperiorDn ) ); 339 } 340 341 next( moveAndRenameContext ); 342 } 343 344 345 /** 346 * {@inheritDoc} 347 */ 348 @Override 349 public void rename( RenameOperationContext renameContext ) throws LdapException 350 { 351 // Normalize the new Rdn and the Dn if needed 352 Dn dn = renameContext.getDn(); 353 354 if ( !dn.isSchemaAware() ) 355 { 356 renameContext.setDn( new Dn( schemaManager, dn ) ); 357 } 358 359 Rdn newRdn = renameContext.getNewRdn(); 360 361 if ( !newRdn.isSchemaAware() ) 362 { 363 renameContext.setNewRdn( new Rdn( schemaManager, newRdn ) ); 364 } 365 366 Dn newDn = renameContext.getNewDn(); 367 368 if ( !newDn.isSchemaAware() ) 369 { 370 renameContext.setNewDn( new Dn( schemaManager, newDn ) ); 371 } 372 373 // Push to the next interceptor 374 next( renameContext ); 375 } 376 377 378 /** 379 * {@inheritDoc} 380 */ 381 @Override 382 public EntryFilteringCursor search( SearchOperationContext searchContext ) throws LdapException 383 { 384 Dn dn = searchContext.getDn(); 385 386 if ( !dn.isSchemaAware() ) 387 { 388 searchContext.setDn( new Dn( schemaManager, dn ) ); 389 } 390 391 ExprNode filter = searchContext.getFilter(); 392 393 if ( filter == null ) 394 { 395 LOG.warn( "undefined filter based on undefined attributeType not evaluted at all. Returning empty enumeration." ); 396 return new EntryFilteringCursorImpl( new EmptyCursor<Entry>(), searchContext, schemaManager ); 397 } 398 399 // Normalize the filter 400 filter = ( ExprNode ) filter.accept( normVisitor ); 401 402 if ( filter == null ) 403 { 404 LOG.warn( "undefined filter based on undefined attributeType not evaluted at all. Returning empty enumeration." ); 405 return new EntryFilteringCursorImpl( new EmptyCursor<Entry>(), searchContext, schemaManager ); 406 } 407 408 // We now have to remove the (ObjectClass=*) filter if it's present, and to add the scope filter 409 ExprNode modifiedFilter = removeObjectClass( filter ); 410 411 searchContext.setFilter( modifiedFilter ); 412 413 // TODO Normalize the returned Attributes, storing the UP attributes to format the returned values. 414 return next( searchContext ); 415 } 416 417 418 /** 419 * Remove the (ObjectClass=*) node from an AndNode, if we have one. 420 */ 421 private ExprNode handleAndNode( ExprNode node ) 422 { 423 int nbNodes = 0; 424 AndNode newAndNode = new AndNode(); 425 426 for ( ExprNode child : ( ( BranchNode ) node ).getChildren() ) 427 { 428 ExprNode modifiedNode = removeObjectClass( child ); 429 430 if ( !( modifiedNode instanceof ObjectClassNode ) ) 431 { 432 newAndNode.addNode( modifiedNode ); 433 nbNodes++; 434 } 435 436 if ( modifiedNode instanceof UndefinedNode ) 437 { 438 // We can just return an Undefined node as nothing will get selected 439 return UndefinedNode.UNDEFINED_NODE; 440 } 441 } 442 443 switch ( nbNodes ) 444 { 445 case 0: 446 // Unlikely... But (&(ObjectClass=*)) or (|(ObjectClass=*)) are still an option 447 return ObjectClassNode.OBJECT_CLASS_NODE; 448 449 case 1: 450 // We can safely remove the AND/OR node and replace it with its first child 451 return newAndNode.getFirstChild(); 452 453 default: 454 return newAndNode; 455 } 456 } 457 458 459 /** 460 * Remove the (ObjectClass=*) node from a NotNode, if we have one. 461 */ 462 private ExprNode handleNotNode( ExprNode node ) 463 { 464 for ( ExprNode child : ( ( BranchNode ) node ).getChildren() ) 465 { 466 ExprNode modifiedNode = removeObjectClass( child ); 467 468 if ( modifiedNode instanceof ObjectClassNode ) 469 { 470 // We don't want any entry which has an ObjectClass, return an undefined node 471 return UndefinedNode.UNDEFINED_NODE; 472 } 473 474 if ( modifiedNode instanceof UndefinedNode ) 475 { 476 // Here, we will select everything 477 return ObjectClassNode.OBJECT_CLASS_NODE; 478 } 479 } 480 481 return node; 482 } 483 484 485 /** 486 * Remove the (ObjectClass=*) node from an OrNode, if we have one. 487 */ 488 private ExprNode handleOrNode( ExprNode node ) 489 { 490 for ( ExprNode child : ( ( BranchNode ) node ).getChildren() ) 491 { 492 ExprNode modifiedNode = removeObjectClass( child ); 493 494 if ( modifiedNode instanceof ObjectClassNode ) 495 { 496 // We can return immediately with an ObjectClass node 497 return ObjectClassNode.OBJECT_CLASS_NODE; 498 } 499 } 500 501 return node; 502 } 503 504 505 /** 506 * Remove the (ObjectClass=*) node from the filter, if we have one. 507 */ 508 private ExprNode removeObjectClass( ExprNode node ) 509 { 510 if ( node instanceof LeafNode ) 511 { 512 LeafNode leafNode = ( LeafNode ) node; 513 514 if ( leafNode.getAttributeType() == directoryService.getAtProvider().getObjectClass() ) 515 { 516 if ( leafNode instanceof PresenceNode ) 517 { 518 // We can safely remove the node and return an undefined node 519 return ObjectClassNode.OBJECT_CLASS_NODE; 520 } 521 else if ( leafNode instanceof EqualityNode ) 522 { 523 Value value = ( ( EqualityNode<String> ) leafNode ).getValue(); 524 525 if ( value.equals( SchemaConstants.TOP_OC ) ) 526 { 527 // Here too we can safely remove the node and return an undefined node 528 return ObjectClassNode.OBJECT_CLASS_NODE; 529 } 530 } 531 } 532 } 533 534 // -------------------------------------------------------------------- 535 // H A N D L E B R A N C H N O D E S 536 // -------------------------------------------------------------------- 537 538 if ( node instanceof AndNode ) 539 { 540 return handleAndNode( node ); 541 } 542 else if ( node instanceof OrNode ) 543 { 544 return handleOrNode( node ); 545 } 546 else if ( node instanceof NotNode ) 547 { 548 return handleNotNode( node ); 549 } 550 else 551 { 552 // Failover : we return the initial node as is 553 return node; 554 } 555 } 556 557 558 // ------------------------------------------------------------------------ 559 // Normalize all Name based arguments for other interface operations 560 // ------------------------------------------------------------------------ 561 /** 562 * Adds missing Rdn's attributes and values to the entry. 563 * 564 * @param dn the Dn 565 * @param entry the entry 566 */ 567 private void addRdnAttributesToEntry( Dn dn, Entry entry ) throws LdapException 568 { 569 if ( dn == null || entry == null ) 570 { 571 return; 572 } 573 574 Rdn rdn = dn.getRdn(); 575 576 // Loop on all the AVAs 577 for ( Ava ava : rdn ) 578 { 579 Value value = ava.getValue(); 580 String upValue = ava.getValue().getString(); 581 String upId = ava.getType(); 582 583 // Check that the entry contains this Ava 584 if ( !entry.contains( upId, value ) ) 585 { 586 String message = "The Rdn '" + upId + "=" + upValue + "' is not present in the entry"; 587 LOG.warn( message ); 588 589 // We don't have this attribute : add it. 590 // Two cases : 591 // 1) The attribute does not exist 592 if ( !entry.containsAttribute( upId ) ) 593 { 594 entry.add( upId, upValue ); 595 } 596 // 2) The attribute exists 597 else 598 { 599 AttributeType at = schemaManager.lookupAttributeTypeRegistry( upId ); 600 601 // 2.1 if the attribute is single valued, replace the value 602 if ( at.isSingleValued() ) 603 { 604 entry.removeAttributes( upId ); 605 entry.add( upId, upValue ); 606 } 607 // 2.2 the attribute is multi-valued : add the missing value 608 else 609 { 610 entry.add( upId, upValue ); 611 } 612 } 613 } 614 } 615 } 616}