001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020 package org.apache.directory.server.core.operational; 021 022 023 import java.util.HashSet; 024 import java.util.Iterator; 025 import java.util.List; 026 import java.util.Set; 027 import java.util.UUID; 028 029 import org.apache.directory.server.constants.ApacheSchemaConstants; 030 import org.apache.directory.server.constants.ServerDNConstants; 031 import org.apache.directory.server.core.DirectoryService; 032 import org.apache.directory.server.core.entry.ClonedServerEntry; 033 import org.apache.directory.server.core.filtering.EntryFilter; 034 import org.apache.directory.server.core.filtering.EntryFilteringCursor; 035 import org.apache.directory.server.core.interceptor.BaseInterceptor; 036 import org.apache.directory.server.core.interceptor.Interceptor; 037 import org.apache.directory.server.core.interceptor.NextInterceptor; 038 import org.apache.directory.server.core.interceptor.context.AddOperationContext; 039 import org.apache.directory.server.core.interceptor.context.ListOperationContext; 040 import org.apache.directory.server.core.interceptor.context.LookupOperationContext; 041 import org.apache.directory.server.core.interceptor.context.ModifyOperationContext; 042 import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext; 043 import org.apache.directory.server.core.interceptor.context.MoveOperationContext; 044 import org.apache.directory.server.core.interceptor.context.RenameOperationContext; 045 import org.apache.directory.server.core.interceptor.context.SearchOperationContext; 046 import org.apache.directory.server.core.interceptor.context.SearchingOperationContext; 047 import org.apache.directory.server.i18n.I18n; 048 import org.apache.directory.shared.ldap.constants.SchemaConstants; 049 import org.apache.directory.shared.ldap.entry.DefaultServerAttribute; 050 import org.apache.directory.shared.ldap.entry.DefaultServerEntry; 051 import org.apache.directory.shared.ldap.entry.EntryAttribute; 052 import org.apache.directory.shared.ldap.entry.Modification; 053 import org.apache.directory.shared.ldap.entry.ModificationOperation; 054 import org.apache.directory.shared.ldap.entry.ServerEntry; 055 import org.apache.directory.shared.ldap.entry.ServerModification; 056 import org.apache.directory.shared.ldap.entry.Value; 057 import org.apache.directory.shared.ldap.exception.LdapSchemaViolationException; 058 import org.apache.directory.shared.ldap.message.ResultCodeEnum; 059 import org.apache.directory.shared.ldap.name.AVA; 060 import org.apache.directory.shared.ldap.name.DN; 061 import org.apache.directory.shared.ldap.name.RDN; 062 import org.apache.directory.shared.ldap.schema.AttributeType; 063 import org.apache.directory.shared.ldap.schema.SchemaManager; 064 import org.apache.directory.shared.ldap.schema.UsageEnum; 065 import org.apache.directory.shared.ldap.util.DateUtils; 066 import org.slf4j.Logger; 067 import org.slf4j.LoggerFactory; 068 069 070 /** 071 * An {@link Interceptor} that adds or modifies the default attributes 072 * of entries. There are four default attributes for now; 073 * <tt>'creatorsName'</tt>, <tt>'createTimestamp'</tt>, <tt>'modifiersName'</tt>, 074 * and <tt>'modifyTimestamp'</tt>. 075 * 076 * @org.apache.xbean.XBean 077 * 078 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 079 * @version $Rev: 927839 $, $Date: 2010-03-26 14:25:10 +0100 (Fri, 26 Mar 2010) $ 080 */ 081 public class OperationalAttributeInterceptor extends BaseInterceptor 082 { 083 /** The LoggerFactory used by this Interceptor */ 084 private static Logger LOG = LoggerFactory.getLogger( OperationalAttributeInterceptor.class ); 085 086 private final EntryFilter DENORMALIZING_SEARCH_FILTER = new EntryFilter() 087 { 088 public boolean accept( SearchingOperationContext operation, ClonedServerEntry serverEntry ) 089 throws Exception 090 { 091 if ( operation.getSearchControls().getReturningAttributes() == null ) 092 { 093 return true; 094 } 095 096 return filterDenormalized( serverEntry ); 097 } 098 }; 099 100 /** 101 * the database search result filter to register with filter service 102 */ 103 private final EntryFilter SEARCH_FILTER = new EntryFilter() 104 { 105 public boolean accept( SearchingOperationContext operation, ClonedServerEntry entry ) 106 throws Exception 107 { 108 return operation.getSearchControls().getReturningAttributes() != null 109 || filterOperationalAttributes( entry ); 110 } 111 }; 112 113 114 private DirectoryService service; 115 116 private DN subschemaSubentryDn; 117 118 /** The schemaManager */ 119 private SchemaManager schemaManager; 120 121 private static AttributeType CREATE_TIMESTAMP_ATTRIBUTE_TYPE; 122 private static AttributeType MODIFIERS_NAME_ATTRIBUTE_TYPE; 123 private static AttributeType MODIFY_TIMESTAMP_ATTRIBUTE_TYPE; 124 125 126 /** 127 * Creates the operational attribute management service interceptor. 128 */ 129 public OperationalAttributeInterceptor() 130 { 131 } 132 133 134 public void init( DirectoryService directoryService ) throws Exception 135 { 136 service = directoryService; 137 schemaManager = directoryService.getSchemaManager(); 138 139 // stuff for dealing with subentries (garbage for now) 140 Value<?> subschemaSubentry = service.getPartitionNexus() 141 .getRootDSE( null ).get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ).get(); 142 subschemaSubentryDn = new DN( subschemaSubentry.getString() ); 143 subschemaSubentryDn.normalize( schemaManager.getNormalizerMapping() ); 144 145 CREATE_TIMESTAMP_ATTRIBUTE_TYPE = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.CREATE_TIMESTAMP_AT ); 146 MODIFIERS_NAME_ATTRIBUTE_TYPE = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.MODIFIERS_NAME_AT ); 147 MODIFY_TIMESTAMP_ATTRIBUTE_TYPE = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.MODIFY_TIMESTAMP_AT ); 148 } 149 150 151 public void destroy() 152 { 153 } 154 155 156 /** 157 * Adds extra operational attributes to the entry before it is added. 158 * 159 * We add those attributes : 160 * - creatorsName 161 * - createTimestamp 162 * - entryCSN 163 * - entryUUID 164 */ 165 public void add( NextInterceptor nextInterceptor, AddOperationContext opContext ) 166 throws Exception 167 { 168 String principal = getPrincipal().getName(); 169 170 ServerEntry entry = opContext.getEntry(); 171 172 /* 173 * @TODO : This code was probably created while working on Mitosis. Most probably dead code. Commented. 174 * Check JIRA DIRSERVER-1416 175 if ( opContext.getEntry().containsAttribute( CREATE_TIMESTAMP_ATTRIBUTE_TYPE ) ) 176 { 177 // As we already have a CreateTimeStamp value in the context, use it, but only if 178 // the principal is admin 179 if ( opContext.getSession().getAuthenticatedPrincipal().getName().equals( 180 ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED )) 181 { 182 entry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() ); 183 } 184 else 185 { 186 String message = "The CreateTimeStamp attribute cannot be created by a user"; 187 LOG.error( message ); 188 throw new LdapSchemaViolationException( message, ResultCodeEnum.INSUFFICIENT_ACCESS_RIGHTS ); 189 } 190 } 191 else 192 { 193 entry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() ); 194 } 195 */ 196 197 // Add the UUID and the entryCSN. The UUID is stored as a byte[] representation of 198 // its String value 199 // @TODO : If we are using replication, those four OAs may be already present. 200 // We have to deal with this as soon as we have the replication working again 201 202 // Check that we don't have an entryUUID AT in the incoming entry, as it's a NO-USER-MODIFICATION AT 203 // Of course, we will allow if for replication (see above @TODO) 204 boolean isAdmin = opContext.getSession().getAuthenticatedPrincipal().getName().equals( 205 ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED ); 206 207 if ( entry.containsAttribute( SchemaConstants.ENTRY_UUID_AT ) ) 208 { 209 if ( !isAdmin ) 210 { 211 // Wrong ! 212 String message = I18n.err( I18n.ERR_30, SchemaConstants.ENTRY_UUID_AT ); 213 LOG.error( message ); 214 throw new LdapSchemaViolationException( ResultCodeEnum.INSUFFICIENT_ACCESS_RIGHTS, message ); 215 } 216 } 217 else 218 { 219 entry.put( SchemaConstants.ENTRY_UUID_AT, UUID.randomUUID().toString() ); 220 } 221 222 if ( entry.containsAttribute( SchemaConstants.ENTRY_CSN_AT ) ) 223 { 224 if ( !isAdmin ) 225 { 226 // Wrong ! 227 String message = I18n.err( I18n.ERR_30, SchemaConstants.ENTRY_CSN_AT ); 228 LOG.error( message ); 229 throw new LdapSchemaViolationException( ResultCodeEnum.INSUFFICIENT_ACCESS_RIGHTS, message ); 230 } 231 } 232 else 233 { 234 entry.put( SchemaConstants.ENTRY_CSN_AT, service.getCSN().toString() ); 235 } 236 237 entry.put( SchemaConstants.CREATORS_NAME_AT, principal ); 238 entry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() ); 239 240 nextInterceptor.add( opContext ); 241 } 242 243 244 public void modify( NextInterceptor nextInterceptor, ModifyOperationContext opContext ) 245 throws Exception 246 { 247 // We must check that the user hasn't injected either the modifiersName 248 // or the modifyTimestamp operational attributes : they are not supposed to be 249 // added at this point. 250 // If so, remove them, and if there are no more attributes, simply return. 251 // otherwise, inject those values into the list of modifications 252 List<Modification> mods = opContext.getModItems(); 253 254 for ( Modification modification: mods ) 255 { 256 AttributeType attributeType = modification.getAttribute().getAttributeType(); 257 258 if ( attributeType.equals( MODIFIERS_NAME_ATTRIBUTE_TYPE ) ) 259 { 260 String message = I18n.err( I18n.ERR_31 ); 261 LOG.error( message ); 262 throw new LdapSchemaViolationException( ResultCodeEnum.INSUFFICIENT_ACCESS_RIGHTS, message ); 263 } 264 265 if ( attributeType.equals( MODIFY_TIMESTAMP_ATTRIBUTE_TYPE ) ) 266 { 267 String message = I18n.err( I18n.ERR_32 ); 268 LOG.error( message ); 269 throw new LdapSchemaViolationException( ResultCodeEnum.INSUFFICIENT_ACCESS_RIGHTS, message ); 270 } 271 } 272 273 // Inject the ModifiersName AT if it's not present 274 EntryAttribute attribute = new DefaultServerAttribute( 275 MODIFIERS_NAME_ATTRIBUTE_TYPE, 276 getPrincipal().getName()); 277 278 Modification modifiersName = new ServerModification( ModificationOperation.REPLACE_ATTRIBUTE, attribute ); 279 280 mods.add( modifiersName ); 281 282 // Inject the ModifyTimestamp AT if it's not present 283 attribute = new DefaultServerAttribute( 284 MODIFY_TIMESTAMP_ATTRIBUTE_TYPE, 285 DateUtils.getGeneralizedTime() ); 286 287 Modification timestamp = new ServerModification( ModificationOperation.REPLACE_ATTRIBUTE, attribute ); 288 289 mods.add( timestamp ); 290 291 // Go down in the chain 292 nextInterceptor.modify( opContext ); 293 294 if ( opContext.getDn().getNormName().equals( subschemaSubentryDn.getNormName() ) ) 295 { 296 return; 297 } 298 299 // ------------------------------------------------------------------- 300 // Add the operational attributes for the modifier first 301 // ------------------------------------------------------------------- 302 // TODO : Why can't we add those elements on teh original modifications ??? 303 // Or into the context ? 304 /* 305 List<Modification> modItemList = new ArrayList<Modification>(2); 306 307 AttributeType modifiersNameAt = atRegistry.lookup( SchemaConstants.MODIFIERS_NAME_AT ); 308 ServerAttribute attribute = new DefaultServerAttribute( 309 modifiersNameAt, 310 getPrincipal().getName()); 311 312 Modification modifiers = new ServerModification( ModificationOperation.REPLACE_ATTRIBUTE, attribute ); 313 modItemList.add( modifiers ); 314 315 AttributeType modifyTimeStampAt = atRegistry.lookup( SchemaConstants.MODIFY_TIMESTAMP_AT ); 316 attribute = new DefaultServerAttribute( 317 modifyTimeStampAt, 318 DateUtils.getGeneralizedTime() ); 319 320 Modification timestamp = new ServerModification( ModificationOperation.REPLACE_ATTRIBUTE, attribute ); 321 modItemList.add( timestamp ); 322 323 // ------------------------------------------------------------------- 324 // Make the modify() call happen 325 // ------------------------------------------------------------------- 326 ModifyOperationContext newModify = new ModifyOperationContext( opContext.getSession(), 327 opContext.getDn(), modItemList ); 328 newModify.setEntry( opContext.getAlteredEntry() ); 329 service.getPartitionNexus().modify( newModify ); 330 */ 331 } 332 333 334 public void rename( NextInterceptor nextInterceptor, RenameOperationContext opContext ) 335 throws Exception 336 { 337 nextInterceptor.rename( opContext ); 338 339 DN newDn = opContext.getNewDn(); 340 341 // add operational attributes after call in case the operation fails 342 ServerEntry serverEntry = new DefaultServerEntry( schemaManager, newDn ); 343 serverEntry.put( SchemaConstants.MODIFIERS_NAME_AT, getPrincipal().getName() ); 344 serverEntry.put( SchemaConstants.MODIFY_TIMESTAMP_AT, DateUtils.getGeneralizedTime() ); 345 346 List<Modification> items = ModifyOperationContext.createModItems( serverEntry, ModificationOperation.REPLACE_ATTRIBUTE ); 347 348 ModifyOperationContext newModify = new ModifyOperationContext( opContext.getSession(), newDn, items ); 349 newModify.setEntry( opContext.getAlteredEntry() ); 350 351 service.getPartitionNexus().modify( newModify ); 352 } 353 354 355 public void move( NextInterceptor nextInterceptor, MoveOperationContext opContext ) throws Exception 356 { 357 nextInterceptor.move( opContext ); 358 359 // add operational attributes after call in case the operation fails 360 ServerEntry serverEntry = new DefaultServerEntry( schemaManager, opContext.getDn() ); 361 serverEntry.put( SchemaConstants.MODIFIERS_NAME_AT, getPrincipal().getName() ); 362 serverEntry.put( SchemaConstants.MODIFY_TIMESTAMP_AT, DateUtils.getGeneralizedTime() ); 363 364 List<Modification> items = ModifyOperationContext.createModItems( serverEntry, ModificationOperation.REPLACE_ATTRIBUTE ); 365 366 367 ModifyOperationContext newModify = 368 new ModifyOperationContext( opContext.getSession(), opContext.getParent(), items ); 369 370 service.getPartitionNexus().modify( newModify ); 371 } 372 373 374 public void moveAndRename( NextInterceptor nextInterceptor, MoveAndRenameOperationContext opContext ) 375 throws Exception 376 { 377 nextInterceptor.moveAndRename( opContext ); 378 379 // add operational attributes after call in case the operation fails 380 ServerEntry serverEntry = new DefaultServerEntry( schemaManager, opContext.getDn() ); 381 serverEntry.put( SchemaConstants.MODIFIERS_NAME_AT, getPrincipal().getName() ); 382 serverEntry.put( SchemaConstants.MODIFY_TIMESTAMP_AT, DateUtils.getGeneralizedTime() ); 383 384 List<Modification> items = ModifyOperationContext.createModItems( serverEntry, ModificationOperation.REPLACE_ATTRIBUTE ); 385 386 ModifyOperationContext newModify = 387 new ModifyOperationContext( opContext.getSession(), opContext.getParent(), items ); 388 389 service.getPartitionNexus().modify( newModify ); 390 } 391 392 393 public ClonedServerEntry lookup( NextInterceptor nextInterceptor, LookupOperationContext opContext ) throws Exception 394 { 395 ClonedServerEntry result = nextInterceptor.lookup( opContext ); 396 397 if ( result == null ) 398 { 399 return null; 400 } 401 402 if ( opContext.getAttrsId() == null ) 403 { 404 filterOperationalAttributes( result ); 405 } 406 else if ( ( opContext.getAllOperational() == null ) || ( opContext.getAllOperational() == false ) ) 407 { 408 filter( opContext, result ); 409 } 410 411 denormalizeEntryOpAttrs( result ); 412 return result; 413 } 414 415 416 public EntryFilteringCursor list( NextInterceptor nextInterceptor, ListOperationContext opContext ) throws Exception 417 { 418 EntryFilteringCursor cursor = nextInterceptor.list( opContext ); 419 cursor.addEntryFilter( SEARCH_FILTER ); 420 return cursor; 421 } 422 423 424 public EntryFilteringCursor search( NextInterceptor nextInterceptor, SearchOperationContext opContext ) throws Exception 425 { 426 EntryFilteringCursor cursor = nextInterceptor.search( opContext ); 427 428 if ( opContext.isAllOperationalAttributes() || 429 ( opContext.getReturningAttributes() != null && ! opContext.getReturningAttributes().isEmpty() ) ) 430 { 431 if ( service.isDenormalizeOpAttrsEnabled() ) 432 { 433 cursor.addEntryFilter( DENORMALIZING_SEARCH_FILTER ); 434 } 435 436 return cursor; 437 } 438 439 cursor.addEntryFilter( SEARCH_FILTER ); 440 return cursor; 441 } 442 443 444 /** 445 * Filters out the operational attributes within a search results attributes. The attributes are directly 446 * modified. 447 * 448 * @param attributes the resultant attributes to filter 449 * @return true always 450 * @throws Exception if there are failures in evaluation 451 */ 452 private boolean filterOperationalAttributes( ServerEntry attributes ) throws Exception 453 { 454 Set<AttributeType> removedAttributes = new HashSet<AttributeType>(); 455 456 // Build a list of attributeType to remove 457 for ( AttributeType attributeType:attributes.getAttributeTypes() ) 458 { 459 if ( attributeType.getUsage() != UsageEnum.USER_APPLICATIONS ) 460 { 461 removedAttributes.add( attributeType ); 462 } 463 } 464 465 // Now remove the attributes which are not USERs 466 for ( AttributeType attributeType:removedAttributes ) 467 { 468 attributes.removeAttributes( attributeType ); 469 } 470 471 return true; 472 } 473 474 475 private void filter( LookupOperationContext lookupContext, ServerEntry entry ) throws Exception 476 { 477 DN dn = lookupContext.getDn(); 478 List<String> ids = lookupContext.getAttrsId(); 479 480 // still need to protect against returning op attrs when ids is null 481 if ( ids == null || ids.isEmpty() ) 482 { 483 filterOperationalAttributes( entry ); 484 return; 485 } 486 487 Set<AttributeType> attributeTypes = entry.getAttributeTypes(); 488 489 if ( dn.size() == 0 ) 490 { 491 for ( AttributeType attributeType:attributeTypes ) 492 { 493 if ( !ids.contains( attributeType.getOid() ) ) 494 { 495 entry.removeAttributes( attributeType ); 496 } 497 } 498 } 499 500 denormalizeEntryOpAttrs( entry ); 501 502 // do nothing past here since this explicity specifies which 503 // attributes to include - backends will automatically populate 504 // with right set of attributes using ids array 505 } 506 507 508 public void denormalizeEntryOpAttrs( ServerEntry entry ) throws Exception 509 { 510 if ( service.isDenormalizeOpAttrsEnabled() ) 511 { 512 EntryAttribute attr = entry.get( SchemaConstants.CREATORS_NAME_AT ); 513 514 if ( attr != null ) 515 { 516 DN creatorsName = new DN( attr.getString() ); 517 518 attr.clear(); 519 attr.add( denormalizeTypes( creatorsName ).getName() ); 520 } 521 522 attr = entry.get( SchemaConstants.MODIFIERS_NAME_AT ); 523 524 if ( attr != null ) 525 { 526 DN modifiersName = new DN( attr.getString() ); 527 528 attr.clear(); 529 attr.add( denormalizeTypes( modifiersName ).getName() ); 530 } 531 532 attr = entry.get( ApacheSchemaConstants.SCHEMA_MODIFIERS_NAME_AT ); 533 534 if ( attr != null ) 535 { 536 DN modifiersName = new DN( attr.getString() ); 537 538 attr.clear(); 539 attr.add( denormalizeTypes( modifiersName ).getName() ); 540 } 541 } 542 } 543 544 545 /** 546 * Does not create a new DN but alters existing DN by using the first 547 * short name for an attributeType definition. 548 * 549 * @param dn the normalized distinguished name 550 * @return the distinuished name denormalized 551 * @throws Exception if there are problems denormalizing 552 */ 553 public DN denormalizeTypes( DN dn ) throws Exception 554 { 555 DN newDn = new DN(); 556 557 for ( int ii = 0; ii < dn.size(); ii++ ) 558 { 559 RDN rdn = dn.getRdn( ii ); 560 if ( rdn.size() == 0 ) 561 { 562 newDn.add( new RDN() ); 563 continue; 564 } 565 else if ( rdn.size() == 1 ) 566 { 567 String name = schemaManager.lookupAttributeTypeRegistry( rdn.getNormType() ).getName(); 568 String value = rdn.getAtav().getNormValue().getString(); 569 newDn.add( new RDN( name, name, value, value ) ); 570 continue; 571 } 572 573 // below we only process multi-valued rdns 574 StringBuffer buf = new StringBuffer(); 575 576 for ( Iterator<AVA> atavs = rdn.iterator(); atavs.hasNext(); /**/ ) 577 { 578 AVA atav = atavs.next(); 579 String type = schemaManager.lookupAttributeTypeRegistry( rdn.getNormType() ).getName(); 580 buf.append( type ).append( '=' ).append( atav.getNormValue() ); 581 582 if ( atavs.hasNext() ) 583 { 584 buf.append( '+' ); 585 } 586 } 587 588 newDn.add( new RDN(buf.toString()) ); 589 } 590 591 return newDn; 592 } 593 594 595 private boolean filterDenormalized( ServerEntry entry ) throws Exception 596 { 597 denormalizeEntryOpAttrs( entry ); 598 return true; 599 } 600 }