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.authz; 021 022 023 import java.text.ParseException; 024 import java.util.ArrayList; 025 import java.util.Collection; 026 import java.util.Collections; 027 import java.util.HashSet; 028 import java.util.List; 029 import java.util.Set; 030 031 import javax.naming.directory.SearchControls; 032 033 import org.apache.directory.server.constants.ServerDNConstants; 034 import org.apache.directory.server.core.CoreSession; 035 import org.apache.directory.server.core.DefaultCoreSession; 036 import org.apache.directory.server.core.DirectoryService; 037 import org.apache.directory.server.core.LdapPrincipal; 038 import org.apache.directory.server.core.authz.support.ACDFEngine; 039 import org.apache.directory.server.core.entry.ClonedServerEntry; 040 import org.apache.directory.server.core.entry.ServerEntryUtils; 041 import org.apache.directory.server.core.filtering.EntryFilter; 042 import org.apache.directory.server.core.filtering.EntryFilteringCursor; 043 import org.apache.directory.server.core.interceptor.BaseInterceptor; 044 import org.apache.directory.server.core.interceptor.InterceptorChain; 045 import org.apache.directory.server.core.interceptor.NextInterceptor; 046 import org.apache.directory.server.core.interceptor.context.AddOperationContext; 047 import org.apache.directory.server.core.interceptor.context.CompareOperationContext; 048 import org.apache.directory.server.core.interceptor.context.DeleteOperationContext; 049 import org.apache.directory.server.core.interceptor.context.EntryOperationContext; 050 import org.apache.directory.server.core.interceptor.context.GetMatchedNameOperationContext; 051 import org.apache.directory.server.core.interceptor.context.ListOperationContext; 052 import org.apache.directory.server.core.interceptor.context.LookupOperationContext; 053 import org.apache.directory.server.core.interceptor.context.ModifyOperationContext; 054 import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext; 055 import org.apache.directory.server.core.interceptor.context.MoveOperationContext; 056 import org.apache.directory.server.core.interceptor.context.OperationContext; 057 import org.apache.directory.server.core.interceptor.context.RenameOperationContext; 058 import org.apache.directory.server.core.interceptor.context.SearchOperationContext; 059 import org.apache.directory.server.core.interceptor.context.SearchingOperationContext; 060 import org.apache.directory.server.core.partition.ByPassConstants; 061 import org.apache.directory.server.core.subtree.SubentryInterceptor; 062 import org.apache.directory.server.i18n.I18n; 063 import org.apache.directory.shared.ldap.aci.ACIItem; 064 import org.apache.directory.shared.ldap.aci.ACIItemParser; 065 import org.apache.directory.shared.ldap.aci.ACITuple; 066 import org.apache.directory.shared.ldap.aci.MicroOperation; 067 import org.apache.directory.shared.ldap.constants.AuthenticationLevel; 068 import org.apache.directory.shared.ldap.constants.SchemaConstants; 069 import org.apache.directory.shared.ldap.entry.EntryAttribute; 070 import org.apache.directory.shared.ldap.entry.Modification; 071 import org.apache.directory.shared.ldap.entry.ServerEntry; 072 import org.apache.directory.shared.ldap.entry.Value; 073 import org.apache.directory.shared.ldap.exception.LdapNoPermissionException; 074 import org.apache.directory.shared.ldap.exception.LdapOperationErrorException; 075 import org.apache.directory.shared.ldap.name.DN; 076 import org.apache.directory.shared.ldap.schema.AttributeType; 077 import org.apache.directory.shared.ldap.schema.SchemaManager; 078 import org.apache.directory.shared.ldap.schema.normalizers.ConcreteNameComponentNormalizer; 079 import org.slf4j.Logger; 080 import org.slf4j.LoggerFactory; 081 082 083 /** 084 * An ACI based authorization service. 085 * 086 * @org.apache.xbean.XBean 087 * 088 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 089 * @version $Rev: 928938 $ 090 */ 091 public class AciAuthorizationInterceptor extends BaseInterceptor 092 { 093 /** the logger for this class */ 094 private static final Logger LOG = LoggerFactory.getLogger( AciAuthorizationInterceptor.class ); 095 096 /** 097 * the multivalued op attr used to track the prescriptive access control 098 * subentries that apply to an entry. 099 */ 100 private static final String AC_SUBENTRY_ATTR = "accessControlSubentries"; 101 102 private static final Collection<MicroOperation> ADD_PERMS; 103 private static final Collection<MicroOperation> READ_PERMS; 104 private static final Collection<MicroOperation> COMPARE_PERMS; 105 private static final Collection<MicroOperation> SEARCH_ENTRY_PERMS; 106 private static final Collection<MicroOperation> SEARCH_ATTRVAL_PERMS; 107 private static final Collection<MicroOperation> REMOVE_PERMS; 108 private static final Collection<MicroOperation> MATCHEDNAME_PERMS; 109 private static final Collection<MicroOperation> BROWSE_PERMS; 110 private static final Collection<MicroOperation> LOOKUP_PERMS; 111 private static final Collection<MicroOperation> REPLACE_PERMS; 112 private static final Collection<MicroOperation> RENAME_PERMS; 113 private static final Collection<MicroOperation> EXPORT_PERMS; 114 private static final Collection<MicroOperation> IMPORT_PERMS; 115 private static final Collection<MicroOperation> MOVERENAME_PERMS; 116 117 static 118 { 119 Set<MicroOperation> set = new HashSet<MicroOperation>( 2 ); 120 set.add( MicroOperation.BROWSE ); 121 set.add( MicroOperation.RETURN_DN ); 122 SEARCH_ENTRY_PERMS = Collections.unmodifiableCollection( set ); 123 124 set = new HashSet<MicroOperation>( 2 ); 125 set.add( MicroOperation.READ ); 126 set.add( MicroOperation.BROWSE ); 127 LOOKUP_PERMS = Collections.unmodifiableCollection( set ); 128 129 set = new HashSet<MicroOperation>( 2 ); 130 set.add( MicroOperation.ADD ); 131 set.add( MicroOperation.REMOVE ); 132 REPLACE_PERMS = Collections.unmodifiableCollection( set ); 133 134 set = new HashSet<MicroOperation>( 2 ); 135 set.add( MicroOperation.EXPORT ); 136 set.add( MicroOperation.RENAME ); 137 MOVERENAME_PERMS = Collections.unmodifiableCollection( set ); 138 139 SEARCH_ATTRVAL_PERMS = Collections.singleton( MicroOperation.READ ); 140 ADD_PERMS = Collections.singleton( MicroOperation.ADD ); 141 READ_PERMS = Collections.singleton( MicroOperation.READ ); 142 COMPARE_PERMS = Collections.singleton( MicroOperation.COMPARE ); 143 REMOVE_PERMS = Collections.singleton( MicroOperation.REMOVE ); 144 MATCHEDNAME_PERMS = Collections.singleton( MicroOperation.DISCLOSE_ON_ERROR ); 145 BROWSE_PERMS = Collections.singleton( MicroOperation.BROWSE ); 146 RENAME_PERMS = Collections.singleton( MicroOperation.RENAME ); 147 EXPORT_PERMS = Collections.singleton( MicroOperation.EXPORT ); 148 IMPORT_PERMS = Collections.singleton( MicroOperation.IMPORT ); 149 } 150 151 /** a tupleCache that responds to add, delete, and modify attempts */ 152 private TupleCache tupleCache; 153 154 /** a groupCache that responds to add, delete, and modify attempts */ 155 private GroupCache groupCache; 156 157 /** a normalizing ACIItem parser */ 158 private ACIItemParser aciParser; 159 160 /** use and instance of the ACDF engine */ 161 private ACDFEngine engine; 162 163 /** interceptor chain */ 164 private InterceptorChain chain; 165 166 /** Global registries */ 167 private SchemaManager schemaManager; 168 169 /** the system wide subschemaSubentryDn */ 170 private String subschemaSubentryDn; 171 172 private AttributeType objectClassType; 173 private AttributeType acSubentryType; 174 175 private String subentryOid; 176 177 /** A storage for the entryACI attributeType */ 178 private AttributeType entryAciType; 179 180 /** the subentry ACI attribute type */ 181 private AttributeType subentryAciType; 182 183 public static final SearchControls DEFAULT_SEARCH_CONTROLS = new SearchControls(); 184 185 /** 186 * Initializes this interceptor based service by getting a handle on the nexus, setting up 187 * the tupe and group membership caches and the ACIItem parser and the ACDF engine. 188 * 189 * @param directoryService the directory service core 190 * @throws Exception if there are problems during initialization 191 */ 192 public void init( DirectoryService directoryService ) throws Exception 193 { 194 super.init( directoryService ); 195 196 DN adminDn = new DN( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED ); 197 adminDn.normalize( directoryService.getSchemaManager().getNormalizerMapping() ); 198 CoreSession adminSession = new DefaultCoreSession( 199 new LdapPrincipal( adminDn, AuthenticationLevel.STRONG ), directoryService ); 200 201 tupleCache = new TupleCache( adminSession ); 202 groupCache = new GroupCache( adminSession ); 203 schemaManager = directoryService.getSchemaManager(); 204 //ocRegistry = registries.getObjectClassRegistry(); 205 206 // look up some constant information 207 String objectClassOid = schemaManager.getAttributeTypeRegistry().getOidByName( SchemaConstants.OBJECT_CLASS_AT ); 208 subentryOid = schemaManager.getObjectClassRegistry().getOidByName( SchemaConstants.SUBENTRY_OC ); 209 String acSubentryOid = schemaManager.getAttributeTypeRegistry().getOidByName( AC_SUBENTRY_ATTR ); 210 objectClassType = schemaManager.lookupAttributeTypeRegistry( objectClassOid ); 211 acSubentryType = schemaManager.lookupAttributeTypeRegistry( acSubentryOid ); 212 entryAciType = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.ENTRY_ACI_AT_OID ); 213 subentryAciType = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.SUBENTRY_ACI_AT_OID ); 214 215 aciParser = new ACIItemParser( new ConcreteNameComponentNormalizer( schemaManager ), schemaManager.getNormalizerMapping() ); 216 engine = new ACDFEngine( schemaManager.getGlobalOidRegistry(), schemaManager ); 217 chain = directoryService.getInterceptorChain(); 218 219 // stuff for dealing with subentries (garbage for now) 220 Value<?> subschemaSubentry = 221 directoryService.getPartitionNexus().getRootDSE( null ). 222 get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ).get(); 223 DN subschemaSubentryDnName = new DN( subschemaSubentry.getString() ); 224 subschemaSubentryDnName.normalize( schemaManager.getNormalizerMapping() ); 225 subschemaSubentryDn = subschemaSubentryDnName.getNormName(); 226 } 227 228 229 private void protectCriticalEntries( DN dn ) throws Exception 230 { 231 DN principalDn = getPrincipal().getClonedName(); 232 233 if ( dn.isEmpty() ) 234 { 235 String msg = I18n.err( I18n.ERR_8 ); 236 LOG.error( msg ); 237 throw new LdapNoPermissionException( msg ); 238 } 239 240 if ( isTheAdministrator( dn ) ) 241 { 242 String msg = I18n.err( I18n.ERR_9, principalDn.getName(), dn.getName() ); 243 LOG.error( msg ); 244 throw new LdapNoPermissionException( msg ); 245 } 246 } 247 248 249 /** 250 * Adds perscriptiveACI tuples to a collection of tuples by accessing the 251 * tupleCache. The tuple cache is accessed for each A/C subentry 252 * associated with the protected entry. Note that subentries are handled 253 * differently: their parent, the administrative entry is accessed to 254 * determine the perscriptiveACIs effecting the AP and hence the subentry 255 * which is considered to be in the same context. 256 * 257 * @param tuples the collection of tuples to add to 258 * @param dn the normalized distinguished name of the protected entry 259 * @param entry the target entry that access to is being controled 260 * @throws Exception if there are problems accessing attribute values 261 * @param proxy the partition nexus proxy object 262 */ 263 private void addPerscriptiveAciTuples( OperationContext opContext, Collection<ACITuple> tuples, DN dn, 264 ServerEntry entry ) throws Exception 265 { 266 EntryAttribute oc = null; 267 268 if ( entry instanceof ClonedServerEntry ) 269 { 270 oc = ((ClonedServerEntry)entry).getOriginalEntry().get( objectClassType ); 271 } 272 else 273 { 274 oc = entry.get( objectClassType ); 275 } 276 277 /* 278 * If the protected entry is a subentry, then the entry being evaluated 279 * for perscriptiveACIs is in fact the administrative entry. By 280 * substituting the administrative entry for the actual subentry the 281 * code below this "if" statement correctly evaluates the effects of 282 * perscriptiveACI on the subentry. Basically subentries are considered 283 * to be in the same naming context as their access point so the subentries 284 * effecting their parent entry applies to them as well. 285 */ 286 if ( oc.contains( SchemaConstants.SUBENTRY_OC ) || oc.contains( subentryOid ) ) 287 { 288 DN parentDn = ( DN ) dn.clone(); 289 parentDn.remove( dn.size() - 1 ); 290 entry = opContext.lookup( parentDn, ByPassConstants.LOOKUP_BYPASS ); 291 } 292 293 EntryAttribute subentries = entry.get( acSubentryType ); 294 295 if ( subentries == null ) 296 { 297 return; 298 } 299 300 for ( Value<?> value:subentries ) 301 { 302 String subentryDn = value.getString(); 303 tuples.addAll( tupleCache.getACITuples( subentryDn ) ); 304 } 305 } 306 307 308 /** 309 * Adds the set of entryACI tuples to a collection of tuples. The entryACI 310 * is parsed and tuples are generated on they fly then added to the collection. 311 * 312 * @param tuples the collection of tuples to add to 313 * @param entry the target entry that access to is being regulated 314 * @throws Exception if there are problems accessing attribute values 315 */ 316 private void addEntryAciTuples( Collection<ACITuple> tuples, ServerEntry entry ) throws Exception 317 { 318 EntryAttribute entryAci = entry.get( entryAciType ); 319 320 if ( entryAci == null ) 321 { 322 return; 323 } 324 325 for ( Value<?> value:entryAci ) 326 { 327 String aciString = value.getString(); 328 ACIItem item; 329 330 try 331 { 332 item = aciParser.parse( aciString ); 333 } 334 catch ( ParseException e ) 335 { 336 String msg = I18n.err( I18n.ERR_10, aciString ); 337 LOG.error( msg, e ); 338 throw new LdapOperationErrorException( msg ); 339 } 340 341 tuples.addAll( item.toTuples() ); 342 } 343 } 344 345 346 /** 347 * Adds the set of subentryACI tuples to a collection of tuples. The subentryACI 348 * is parsed and tuples are generated on the fly then added to the collection. 349 * 350 * @param tuples the collection of tuples to add to 351 * @param dn the normalized distinguished name of the protected entry 352 * @param entry the target entry that access to is being regulated 353 * @throws Exception if there are problems accessing attribute values 354 * @param proxy the partition nexus proxy object 355 */ 356 private void addSubentryAciTuples( OperationContext opContext, Collection<ACITuple> tuples, DN dn, ServerEntry entry ) 357 throws Exception 358 { 359 // only perform this for subentries 360 if ( !entry.contains( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.SUBENTRY_OC ) ) 361 { 362 return; 363 } 364 365 // get the parent or administrative entry for this subentry since it 366 // will contain the subentryACI attributes that effect subentries 367 DN parentDn = ( DN ) dn.clone(); 368 parentDn.remove( dn.size() - 1 ); 369 ServerEntry administrativeEntry = opContext.lookup( parentDn, ByPassConstants.LOOKUP_BYPASS ).getOriginalEntry(); 370 371 EntryAttribute subentryAci = administrativeEntry.get( subentryAciType ); 372 373 if ( subentryAci == null ) 374 { 375 return; 376 } 377 378 for ( Value<?> value:subentryAci ) 379 { 380 String aciString = value.getString(); 381 ACIItem item; 382 383 try 384 { 385 item = aciParser.parse( aciString ); 386 } 387 catch ( ParseException e ) 388 { 389 String msg = I18n.err( I18n.ERR_11, aciString ); 390 LOG.error( msg, e ); 391 throw new LdapOperationErrorException( msg ); 392 } 393 394 tuples.addAll( item.toTuples() ); 395 } 396 } 397 398 399 /* ------------------------------------------------------------------------------- 400 * Within every access controled interceptor method we must retrieve the ACITuple 401 * set for all the perscriptiveACIs that apply to the candidate, the target entry 402 * operated upon. This ACITuple set is gotten from the TupleCache by looking up 403 * the subentries referenced by the accessControlSubentries operational attribute 404 * within the target entry. 405 * 406 * Then the entry is inspected for an entryACI. This is not done for the add op 407 * since it could introduce a security breech. So for non-add ops if present a 408 * set of ACITuples are generated for all the entryACIs within the entry. This 409 * set is combined with the ACITuples cached for the perscriptiveACI affecting 410 * the target entry. If the entry is a subentry the ACIs are also processed for 411 * the subentry to generate more ACITuples. This subentry TupleACI set is joined 412 * with the entry and perscriptive ACI. 413 * 414 * The union of ACITuples are fed into the engine along with other parameters 415 * to decide whether a permission is granted or rejected for the specific 416 * operation. 417 * ------------------------------------------------------------------------------- 418 */ 419 420 public void add( NextInterceptor next, AddOperationContext addContext ) throws Exception 421 { 422 // Access the principal requesting the operation, and bypass checks if it is the admin 423 LdapPrincipal principal = addContext.getSession().getEffectivePrincipal(); 424 DN principalDn = principal.getClonedName(); 425 426 ServerEntry serverEntry = addContext.getEntry(); 427 //Attributes entry = ServerEntryUtils.toAttributesImpl( serverEntry ); 428 429 DN name = addContext.getDn(); 430 431 // bypass authz code if we are disabled 432 if ( !addContext.getSession().getDirectoryService().isAccessControlEnabled() ) 433 { 434 next.add( addContext ); 435 return; 436 } 437 438 // bypass authz code but manage caches if operation is performed by the admin 439 if ( isPrincipalAnAdministrator( principalDn ) ) 440 { 441 next.add( addContext ); 442 tupleCache.subentryAdded( name, serverEntry ); 443 groupCache.groupAdded( name, serverEntry ); 444 return; 445 } 446 447 // perform checks below here for all non-admin users 448 SubentryInterceptor subentryInterceptor = ( SubentryInterceptor ) chain.get( SubentryInterceptor.class.getName() ); 449 ServerEntry subentryAttrs = subentryInterceptor.getSubentryAttributes( name, serverEntry ); 450 451 for ( EntryAttribute attribute:serverEntry ) 452 { 453 subentryAttrs.put( attribute ); 454 } 455 456 // Assemble all the information required to make an access control decision 457 Set<DN> userGroups = groupCache.getGroups( principalDn.getNormName() ); 458 Collection<ACITuple> tuples = new HashSet<ACITuple>(); 459 460 // Build the total collection of tuples to be considered for add rights 461 // NOTE: entryACI are NOT considered in adds (it would be a security breech) 462 addPerscriptiveAciTuples( addContext, tuples, name, subentryAttrs ); 463 addSubentryAciTuples( addContext, tuples, name, subentryAttrs ); 464 465 // check if entry scope permission is granted 466 engine.checkPermission( schemaManager, addContext, userGroups, principalDn, principal.getAuthenticationLevel(), name, null, null, 467 ADD_PERMS, tuples, subentryAttrs, null ); 468 469 // now we must check if attribute type and value scope permission is granted 470 for ( EntryAttribute attribute:serverEntry ) 471 { 472 for ( Value<?> value:attribute ) 473 { 474 engine.checkPermission( schemaManager, addContext, userGroups, principalDn, 475 principal.getAuthenticationLevel(), name, attribute.getUpId(), value, 476 ADD_PERMS, tuples, serverEntry, null ); 477 } 478 } 479 480 // if we've gotten this far then access has been granted 481 next.add( addContext ); 482 483 // if the entry added is a subentry or a groupOf[Unique]Names we must 484 // update the ACITuple cache and the groups cache to keep them in sync 485 tupleCache.subentryAdded( name, serverEntry ); 486 groupCache.groupAdded( name, serverEntry ); 487 } 488 489 490 private boolean isTheAdministrator( DN normalizedDn ) 491 { 492 return normalizedDn.getNormName().equals( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED ); 493 } 494 495 496 public void delete( NextInterceptor next, DeleteOperationContext deleteContext ) throws Exception 497 { 498 DN name = deleteContext.getDn(); 499 500 LdapPrincipal principal = deleteContext.getSession().getEffectivePrincipal(); 501 DN principalDn = principal.getClonedName(); 502 503 // bypass authz code if we are disabled 504 if ( ! deleteContext.getSession().getDirectoryService().isAccessControlEnabled() ) 505 { 506 next.delete( deleteContext ); 507 return; 508 } 509 510 ClonedServerEntry entry = deleteContext.lookup( name, ByPassConstants.LOOKUP_BYPASS ); 511 512 protectCriticalEntries( name ); 513 514 // bypass authz code but manage caches if operation is performed by the admin 515 if ( isPrincipalAnAdministrator( principalDn ) ) 516 { 517 next.delete( deleteContext ); 518 tupleCache.subentryDeleted( name, entry ); 519 groupCache.groupDeleted( name, entry ); 520 return; 521 } 522 523 Set<DN> userGroups = groupCache.getGroups( principalDn.getNormName() ); 524 Collection<ACITuple> tuples = new HashSet<ACITuple>(); 525 addPerscriptiveAciTuples( deleteContext, tuples, name, entry.getOriginalEntry() ); 526 addEntryAciTuples( tuples, entry ); 527 addSubentryAciTuples( deleteContext, tuples, name, entry ); 528 529 engine.checkPermission( schemaManager, deleteContext, userGroups, principalDn, 530 principal.getAuthenticationLevel(), name, null, null, REMOVE_PERMS, tuples, entry, null ); 531 532 next.delete( deleteContext ); 533 tupleCache.subentryDeleted( name, entry ); 534 groupCache.groupDeleted( name, entry ); 535 } 536 537 538 public void modify( NextInterceptor next, ModifyOperationContext opContext ) throws Exception 539 { 540 DN name = opContext.getDn(); 541 542 // Access the principal requesting the operation, and bypass checks if it is the admin 543 ClonedServerEntry entry = opContext.lookup( name, ByPassConstants.LOOKUP_BYPASS ); 544 545 LdapPrincipal principal = opContext.getSession().getEffectivePrincipal(); 546 DN principalDn = principal.getClonedName(); 547 548 // bypass authz code if we are disabled 549 if ( !opContext.getSession().getDirectoryService().isAccessControlEnabled() ) 550 { 551 next.modify( opContext ); 552 return; 553 } 554 555 List<Modification> mods = opContext.getModItems(); 556 557 // bypass authz code but manage caches if operation is performed by the admin 558 if ( isPrincipalAnAdministrator( principalDn ) ) 559 { 560 next.modify( opContext ); 561 /** 562 * @TODO: A virtual entry can be created here for not hitting the backend again. 563 */ 564 ServerEntry modifiedEntry = opContext.lookup( name, ByPassConstants.LOOKUP_BYPASS ); 565 tupleCache.subentryModified( name, mods, modifiedEntry ); 566 groupCache.groupModified( name, mods, entry, schemaManager ); 567 return; 568 } 569 570 Set<DN> userGroups = groupCache.getGroups( principalDn.getName() ); 571 Collection<ACITuple> tuples = new HashSet<ACITuple>(); 572 addPerscriptiveAciTuples( opContext, tuples, name, entry.getOriginalEntry() ); 573 addEntryAciTuples( tuples, entry ); 574 addSubentryAciTuples( opContext, tuples, name, entry ); 575 576 engine.checkPermission( schemaManager, opContext, userGroups, principalDn, 577 principal.getAuthenticationLevel(), name, null, null, 578 Collections.singleton( MicroOperation.MODIFY ), tuples, entry, null ); 579 580 Collection<MicroOperation> perms = null; 581 ServerEntry entryView = ( ServerEntry ) entry.clone(); 582 583 for ( Modification mod : mods ) 584 { 585 EntryAttribute attr = mod.getAttribute(); 586 587 switch ( mod.getOperation() ) 588 { 589 case ADD_ATTRIBUTE : 590 perms = ADD_PERMS; 591 592 // If the attribute is being created with an initial value ... 593 if ( entry.get( attr.getId() ) == null ) 594 { 595 // ... we also need to check if adding the attribute is permitted 596 engine.checkPermission( schemaManager, opContext, userGroups, principalDn, principal.getAuthenticationLevel(), name, 597 attr.getId(), null, perms, tuples, entry, null ); 598 } 599 600 break; 601 602 case REMOVE_ATTRIBUTE : 603 perms = REMOVE_PERMS; 604 EntryAttribute entryAttr = entry.get( attr.getId() ); 605 606 if ( entryAttr != null ) 607 { 608 // If there is only one value remaining in the attribute ... 609 if ( entryAttr.size() == 1 ) 610 { 611 // ... we also need to check if removing the attribute at all is permitted 612 engine.checkPermission( schemaManager, opContext, userGroups, principalDn, 613 principal.getAuthenticationLevel(), name, attr.getId(), 614 null, perms, tuples, entry, null ); 615 } 616 } 617 618 break; 619 620 case REPLACE_ATTRIBUTE : 621 perms = REPLACE_PERMS; 622 break; 623 } 624 625 /** 626 * Update the entry view as the current modification is applied to the original entry. 627 * This is especially required for handling the MaxValueCount protected item. Number of 628 * values for an attribute after a modification should be known in advance in order to 629 * check permissions for MaxValueCount protected item. So during addition of the first 630 * value of an attribute it can be rejected if the permission denied due the the 631 * MaxValueCount protected item. This is not the perfect implementation as required by 632 * the specification because the system should reject the addition exactly on the right 633 * value of the attribute. However as we do not have that much granularity in our 634 * implementation (we consider an Attribute Addition itself a Micro Operation, 635 * not the individual Value Additions) we just handle this when the first value of an 636 * attribute is being checked for relevant permissions below. 637 */ 638 entryView = ServerEntryUtils.getTargetEntry( mod, entryView, schemaManager ); 639 640 for ( Value<?> value:attr ) 641 { 642 engine.checkPermission( schemaManager, opContext, userGroups, principalDn, 643 principal.getAuthenticationLevel(), name, attr.getId(), value, 644 perms, tuples, entry, entryView ); 645 } 646 } 647 648 649 650 next.modify( opContext ); 651 /** 652 * @TODO: A virtual entry can be created here for not hitting the backend again. 653 */ 654 ServerEntry modifiedEntry = opContext.lookup( name, ByPassConstants.LOOKUP_BYPASS ); 655 tupleCache.subentryModified( name, mods, modifiedEntry ); 656 groupCache.groupModified( name, mods, entry, schemaManager ); 657 } 658 659 660 public boolean hasEntry( NextInterceptor next, EntryOperationContext entryContext ) throws Exception 661 { 662 DN name = entryContext.getDn(); 663 664 if ( ! entryContext.getSession().getDirectoryService().isAccessControlEnabled() ) 665 { 666 return name.size() == 0 || next.hasEntry( entryContext ); 667 } 668 669 boolean answer = next.hasEntry( entryContext ); 670 671 // no checks on the RootDSE 672 if ( name.size() == 0 ) 673 { 674 // No need to go down to the stack, if the dn is empty 675 // It's the rootDSE, and it exists ! 676 return answer; 677 } 678 679 // TODO - eventually replace this with a check on session.isAnAdministrator() 680 LdapPrincipal principal = entryContext.getSession().getEffectivePrincipal(); 681 DN principalDn = principal.getClonedName(); 682 if ( isPrincipalAnAdministrator( principalDn ) ) 683 { 684 return answer; 685 } 686 687 ClonedServerEntry entry = entryContext.lookup( name, ByPassConstants.HAS_ENTRY_BYPASS ); 688 Set<DN> userGroups = groupCache.getGroups( principalDn.getNormName() ); 689 Collection<ACITuple> tuples = new HashSet<ACITuple>(); 690 addPerscriptiveAciTuples( entryContext, tuples, name, entry.getOriginalEntry() ); 691 addEntryAciTuples( tuples, entry.getOriginalEntry() ); 692 addSubentryAciTuples( entryContext, tuples, name, entry.getOriginalEntry() ); 693 694 // check that we have browse access to the entry 695 engine.checkPermission( schemaManager, entryContext, userGroups, principalDn, 696 principal.getAuthenticationLevel(), name, null, null, 697 BROWSE_PERMS, tuples, entry.getOriginalEntry(), null ); 698 699 return next.hasEntry( entryContext ); 700 } 701 702 703 /** 704 * Checks if the READ permissions exist to the entry and to each attribute type and 705 * value. 706 * 707 * @todo not sure if we should hide attribute types/values or throw an exception 708 * instead. I think we're going to have to use a filter to restrict the return 709 * of attribute types and values instead of throwing an exception. Lack of read 710 * perms to attributes and their values results in their removal when returning 711 * the entry. 712 * 713 * @param principal the user associated with the call 714 * @param dn the name of the entry being looked up 715 * @param entry the raw entry pulled from the nexus 716 * @throws Exception if undlying access to the DIT fails 717 */ 718 private void checkLookupAccess( LookupOperationContext lookupContext, ServerEntry entry ) throws Exception 719 { 720 // no permissions checks on the RootDSE 721 if ( lookupContext.getDn().getNormName().trim().equals( "" ) ) 722 { 723 return; 724 } 725 726 LdapPrincipal principal = lookupContext.getSession().getEffectivePrincipal(); 727 DN userName = principal.getClonedName(); 728 Set<DN> userGroups = groupCache.getGroups( userName.getNormName() ); 729 Collection<ACITuple> tuples = new HashSet<ACITuple>(); 730 addPerscriptiveAciTuples( lookupContext, tuples, lookupContext.getDn(), entry ); 731 addEntryAciTuples( tuples, entry ); 732 addSubentryAciTuples( lookupContext, tuples, lookupContext.getDn(), entry ); 733 734 // check that we have read access to the entry 735 engine.checkPermission( schemaManager, lookupContext, userGroups, userName, principal.getAuthenticationLevel(), 736 lookupContext.getDn(), null, null, 737 LOOKUP_PERMS, tuples, entry, null ); 738 739 // check that we have read access to every attribute type and value 740 for ( EntryAttribute attribute:entry ) 741 { 742 743 for ( Value<?> value:attribute ) 744 { 745 engine.checkPermission( 746 schemaManager, 747 lookupContext, 748 userGroups, 749 userName, 750 principal.getAuthenticationLevel(), 751 lookupContext.getDn(), 752 attribute.getUpId(), 753 value, 754 READ_PERMS, 755 tuples, 756 entry, 757 null ); 758 } 759 } 760 } 761 762 763 public ClonedServerEntry lookup( NextInterceptor next, LookupOperationContext lookupContext ) throws Exception 764 { 765 LdapPrincipal principal = lookupContext.getSession().getEffectivePrincipal(); 766 DN principalDn = principal.getClonedName(); 767 768 if ( !principalDn.isNormalized() ) 769 { 770 principalDn.normalize( schemaManager.getNormalizerMapping() ); 771 } 772 773 if ( isPrincipalAnAdministrator( principalDn ) || !lookupContext.getSession().getDirectoryService().isAccessControlEnabled() ) 774 { 775 return next.lookup( lookupContext ); 776 } 777 778 lookupContext.setByPassed( ByPassConstants.LOOKUP_BYPASS ); 779 ServerEntry entry = lookupContext.getSession().getDirectoryService() 780 .getOperationManager().lookup( lookupContext ); 781 782 checkLookupAccess( lookupContext, entry ); 783 return next.lookup( lookupContext ); 784 } 785 786 787 public void rename( NextInterceptor next, RenameOperationContext renameContext ) throws Exception 788 { 789 DN oldName = renameContext.getDn(); 790 ServerEntry originalEntry = null; 791 792 if ( renameContext.getEntry() != null ) 793 { 794 originalEntry = renameContext.getEntry().getOriginalEntry(); 795 } 796 797 LdapPrincipal principal = renameContext.getSession().getEffectivePrincipal(); 798 DN principalDn = principal.getClonedName(); 799 DN newName = renameContext.getNewDn(); 800 801 // bypass authz code if we are disabled 802 if ( !renameContext.getSession().getDirectoryService().isAccessControlEnabled() ) 803 { 804 next.rename( renameContext ); 805 return; 806 } 807 808 protectCriticalEntries( oldName ); 809 810 // bypass authz code but manage caches if operation is performed by the admin 811 if ( isPrincipalAnAdministrator( principalDn ) ) 812 { 813 next.rename( renameContext ); 814 tupleCache.subentryRenamed( oldName, newName ); 815 816 // TODO : this method returns a boolean : what should we do with the result ? 817 groupCache.groupRenamed( oldName, newName ); 818 819 return; 820 } 821 822 Set<DN> userGroups = groupCache.getGroups( principalDn.getNormName() ); 823 Collection<ACITuple> tuples = new HashSet<ACITuple>(); 824 addPerscriptiveAciTuples( renameContext, tuples, oldName, originalEntry ); 825 addEntryAciTuples( tuples, originalEntry ); 826 addSubentryAciTuples( renameContext, tuples, oldName, originalEntry ); 827 828 engine.checkPermission( schemaManager, renameContext, userGroups, principalDn, 829 principal.getAuthenticationLevel(), oldName, null, null, 830 RENAME_PERMS, tuples, originalEntry, null ); 831 832 next.rename( renameContext ); 833 tupleCache.subentryRenamed( oldName, newName ); 834 groupCache.groupRenamed( oldName, newName ); 835 } 836 837 838 public void moveAndRename( NextInterceptor next, MoveAndRenameOperationContext moveAndRenameContext ) 839 throws Exception 840 { 841 DN oriChildName = moveAndRenameContext.getDn(); 842 DN newParentName = moveAndRenameContext.getParent(); 843 844 ClonedServerEntry entry = moveAndRenameContext.lookup( oriChildName, ByPassConstants.LOOKUP_BYPASS ); 845 846 LdapPrincipal principal = moveAndRenameContext.getSession().getEffectivePrincipal(); 847 DN principalDn = principal.getClonedName(); 848 DN newName = ( DN ) newParentName.clone(); 849 newName.add( moveAndRenameContext.getNewRdn().getName() ); 850 851 // bypass authz code if we are disabled 852 if ( !moveAndRenameContext.getSession().getDirectoryService().isAccessControlEnabled() ) 853 { 854 next.moveAndRename( moveAndRenameContext ); 855 return; 856 } 857 858 protectCriticalEntries( oriChildName ); 859 860 // bypass authz code but manage caches if operation is performed by the admin 861 if ( isPrincipalAnAdministrator( principalDn ) ) 862 { 863 next.moveAndRename( moveAndRenameContext ); 864 tupleCache.subentryRenamed( oriChildName, newName ); 865 groupCache.groupRenamed( oriChildName, newName ); 866 return; 867 } 868 869 Set<DN> userGroups = groupCache.getGroups( principalDn.getNormName() ); 870 Collection<ACITuple> tuples = new HashSet<ACITuple>(); 871 addPerscriptiveAciTuples( moveAndRenameContext, tuples, oriChildName, entry.getOriginalEntry() ); 872 addEntryAciTuples( tuples, entry ); 873 addSubentryAciTuples( moveAndRenameContext, tuples, oriChildName, entry ); 874 875 engine.checkPermission( schemaManager, moveAndRenameContext, userGroups, 876 principalDn, principal.getAuthenticationLevel(), oriChildName, null, 877 null, MOVERENAME_PERMS, tuples, entry, null ); 878 879 // Get the entry again without operational attributes 880 // because access control subentry operational attributes 881 // will not be valid at the new location. 882 // This will certainly be fixed by the SubentryInterceptor, 883 // but after this service. 884 885 ClonedServerEntry importedEntry = moveAndRenameContext.lookup( oriChildName, 886 ByPassConstants.LOOKUP_EXCLUDING_OPR_ATTRS_BYPASS ); 887 888 // As the target entry does not exist yet and so 889 // its subentry operational attributes are not there, 890 // we need to construct an entry to represent it 891 // at least with minimal requirements which are object class 892 // and access control subentry operational attributes. 893 SubentryInterceptor subentryInterceptor = ( SubentryInterceptor ) chain.get( SubentryInterceptor.class.getName() ); 894 ServerEntry subentryAttrs = subentryInterceptor.getSubentryAttributes( newName, importedEntry ); 895 896 for ( EntryAttribute attribute:importedEntry ) 897 { 898 subentryAttrs.put( attribute ); 899 } 900 901 Collection<ACITuple> destTuples = new HashSet<ACITuple>(); 902 // Import permission is only valid for prescriptive ACIs 903 addPerscriptiveAciTuples( moveAndRenameContext, destTuples, newName, subentryAttrs ); 904 // Evaluate the target context to see whether it 905 // allows an entry named newName to be imported as a subordinate. 906 engine.checkPermission( schemaManager, moveAndRenameContext, userGroups, principalDn, 907 principal.getAuthenticationLevel(), newName, null, 908 null, IMPORT_PERMS, destTuples, subentryAttrs, null ); 909 910 911 next.moveAndRename( moveAndRenameContext ); 912 tupleCache.subentryRenamed( oriChildName, newName ); 913 groupCache.groupRenamed( oriChildName, newName ); 914 } 915 916 917 public void move( NextInterceptor next, MoveOperationContext moveContext ) throws Exception 918 { 919 DN oriChildName = moveContext.getDn(); 920 DN newParentName = moveContext.getParent(); 921 922 // Access the principal requesting the operation, and bypass checks if it is the admin 923 ClonedServerEntry entry = moveContext.lookup( oriChildName, ByPassConstants.LOOKUP_BYPASS ); 924 925 DN newName = ( DN ) newParentName.clone(); 926 newName.add( oriChildName.get( oriChildName.size() - 1 ) ); 927 LdapPrincipal principal = moveContext.getSession().getEffectivePrincipal(); 928 DN principalDn = principal.getClonedName(); 929 930 // bypass authz code if we are disabled 931 if ( !moveContext.getSession().getDirectoryService().isAccessControlEnabled() ) 932 { 933 next.move( moveContext ); 934 return; 935 } 936 937 protectCriticalEntries( oriChildName); 938 939 // bypass authz code but manage caches if operation is performed by the admin 940 if ( isPrincipalAnAdministrator( principalDn ) ) 941 { 942 next.move( moveContext ); 943 tupleCache.subentryRenamed( oriChildName, newName ); 944 groupCache.groupRenamed( oriChildName, newName ); 945 return; 946 } 947 948 Set<DN> userGroups = groupCache.getGroups( principalDn.getNormName() ); 949 Collection<ACITuple> tuples = new HashSet<ACITuple>(); 950 addPerscriptiveAciTuples( moveContext, tuples, oriChildName, entry.getOriginalEntry() ); 951 addEntryAciTuples( tuples, entry ); 952 addSubentryAciTuples( moveContext, tuples, oriChildName, entry ); 953 954 engine.checkPermission( schemaManager, moveContext, userGroups, principalDn, 955 principal.getAuthenticationLevel(), oriChildName, null, 956 null, EXPORT_PERMS, tuples, entry, null ); 957 958 // Get the entry again without operational attributes 959 // because access control subentry operational attributes 960 // will not be valid at the new location. 961 // This will certainly be fixed by the SubentryInterceptor, 962 // but after this service. 963 ServerEntry importedEntry = moveContext.lookup( oriChildName, 964 ByPassConstants.LOOKUP_EXCLUDING_OPR_ATTRS_BYPASS ); 965 966 // As the target entry does not exist yet and so 967 // its subentry operational attributes are not there, 968 // we need to construct an entry to represent it 969 // at least with minimal requirements which are object class 970 // and access control subentry operational attributes. 971 SubentryInterceptor subentryInterceptor = ( SubentryInterceptor ) 972 chain.get( SubentryInterceptor.class.getName() ); 973 ServerEntry subentryAttrs = subentryInterceptor.getSubentryAttributes( newName, importedEntry ); 974 975 for ( EntryAttribute attribute:importedEntry ) 976 { 977 subentryAttrs.put( attribute ); 978 } 979 980 Collection<ACITuple> destTuples = new HashSet<ACITuple>(); 981 // Import permission is only valid for prescriptive ACIs 982 addPerscriptiveAciTuples( moveContext, destTuples, newName, subentryAttrs ); 983 // Evaluate the target context to see whether it 984 // allows an entry named newName to be imported as a subordinate. 985 engine.checkPermission( schemaManager, moveContext, userGroups, principalDn, 986 principal.getAuthenticationLevel(), newName, null, 987 null, IMPORT_PERMS, destTuples, subentryAttrs, null ); 988 989 next.move( moveContext ); 990 tupleCache.subentryRenamed( oriChildName, newName ); 991 groupCache.groupRenamed( oriChildName, newName ); 992 } 993 994 995 public EntryFilteringCursor list( NextInterceptor next, ListOperationContext opContext ) throws Exception 996 { 997 LdapPrincipal user = opContext.getSession().getEffectivePrincipal(); 998 EntryFilteringCursor cursor = next.list( opContext ); 999 1000 if ( isPrincipalAnAdministrator( user.getClonedName() ) || !opContext.getSession().getDirectoryService().isAccessControlEnabled() ) 1001 { 1002 return cursor; 1003 } 1004 1005 AuthorizationFilter authzFilter = new AuthorizationFilter(); 1006 cursor.addEntryFilter( authzFilter ); 1007 return cursor; 1008 } 1009 1010 1011 public EntryFilteringCursor search( NextInterceptor next, SearchOperationContext opContext ) throws Exception 1012 { 1013 LdapPrincipal user = opContext.getSession().getEffectivePrincipal(); 1014 DN principalDn = user.getClonedName(); 1015 EntryFilteringCursor cursor = next.search( opContext ); 1016 1017 boolean isSubschemaSubentryLookup = subschemaSubentryDn.equals( opContext.getDn().getNormName() ); 1018 SearchControls searchCtls = opContext.getSearchControls(); 1019 boolean isRootDSELookup = opContext.getDn().size() == 0 && searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE; 1020 1021 if ( isPrincipalAnAdministrator( principalDn ) || !opContext.getSession().getDirectoryService().isAccessControlEnabled() || isRootDSELookup || isSubschemaSubentryLookup ) 1022 { 1023 return cursor; 1024 } 1025 1026 cursor.addEntryFilter( new AuthorizationFilter() ); 1027 return cursor; 1028 } 1029 1030 1031 public final boolean isPrincipalAnAdministrator( DN principalDn ) 1032 { 1033 return groupCache.isPrincipalAnAdministrator( principalDn ); 1034 } 1035 1036 1037 public boolean compare( NextInterceptor next, CompareOperationContext opContext ) throws Exception 1038 { 1039 DN name = opContext.getDn(); 1040 String oid = opContext.getOid(); 1041 Value<?> value = ( Value<?> ) opContext.getValue(); 1042 1043 ClonedServerEntry entry = opContext.lookup( name, ByPassConstants.LOOKUP_BYPASS ); 1044 1045 LdapPrincipal principal = opContext.getSession().getEffectivePrincipal(); 1046 DN principalDn = principal.getClonedName(); 1047 1048 if ( isPrincipalAnAdministrator( principalDn ) || !opContext.getSession().getDirectoryService().isAccessControlEnabled() ) 1049 { 1050 return next.compare( opContext ); 1051 } 1052 1053 Set<DN> userGroups = groupCache.getGroups( principalDn.getNormName() ); 1054 Collection<ACITuple> tuples = new HashSet<ACITuple>(); 1055 addPerscriptiveAciTuples( opContext, tuples, name, entry.getOriginalEntry() ); 1056 addEntryAciTuples( tuples, entry ); 1057 addSubentryAciTuples( opContext, tuples, name, entry ); 1058 1059 engine.checkPermission( schemaManager, opContext, userGroups, principalDn, 1060 principal.getAuthenticationLevel(), name, null, null, 1061 READ_PERMS, tuples, entry, null ); 1062 engine.checkPermission( schemaManager, opContext, userGroups, principalDn, 1063 principal.getAuthenticationLevel(), name, oid, value, 1064 COMPARE_PERMS, tuples, entry, null ); 1065 1066 return next.compare( opContext ); 1067 } 1068 1069 1070 public DN getMatchedName ( NextInterceptor next, GetMatchedNameOperationContext opContext ) throws Exception 1071 { 1072 // Access the principal requesting the operation, and bypass checks if it is the admin 1073 LdapPrincipal principal = opContext.getSession().getEffectivePrincipal(); 1074 DN principalDn = principal.getClonedName(); 1075 1076 if ( isPrincipalAnAdministrator( principalDn ) || !opContext.getSession().getDirectoryService().isAccessControlEnabled() ) 1077 { 1078 return next.getMatchedName( opContext ); 1079 } 1080 1081 // get the present matched name 1082 ClonedServerEntry entry; 1083 DN matched = next.getMatchedName( opContext ); 1084 1085 // check if we have disclose on error permission for the entry at the matched dn 1086 // if not remove rdn and check that until nothing is left in the name and return 1087 // that but if permission is granted then short the process and return the dn 1088 while ( matched.size() > 0 ) 1089 { 1090 entry = opContext.lookup( matched, ByPassConstants.GETMATCHEDDN_BYPASS ); 1091 1092 Set<DN> userGroups = groupCache.getGroups( principalDn.getNormName() ); 1093 Collection<ACITuple> tuples = new HashSet<ACITuple>(); 1094 addPerscriptiveAciTuples( opContext, tuples, matched, entry.getOriginalEntry() ); 1095 addEntryAciTuples( tuples, entry ); 1096 addSubentryAciTuples( opContext, tuples, matched, entry ); 1097 1098 if ( engine.hasPermission( schemaManager, opContext, userGroups, principalDn, 1099 principal.getAuthenticationLevel(), matched, null, 1100 null, MATCHEDNAME_PERMS, tuples, entry, null ) ) 1101 { 1102 return matched; 1103 } 1104 1105 matched.remove( matched.size() - 1 ); 1106 } 1107 1108 return matched; 1109 } 1110 1111 1112 public void cacheNewGroup( DN name, ServerEntry entry ) throws Exception 1113 { 1114 groupCache.groupAdded( name, entry ); 1115 } 1116 1117 1118 private boolean filter( OperationContext opContext, DN normName, ClonedServerEntry clonedEntry ) 1119 throws Exception 1120 { 1121 /* 1122 * First call hasPermission() for entry level "Browse" and "ReturnDN" perm 1123 * tests. If we hasPermission() returns false we immediately short the 1124 * process and return false. 1125 */ 1126 1127 LdapPrincipal principal = opContext.getSession().getEffectivePrincipal(); 1128 DN userDn = principal.getClonedName(); 1129 Set<DN> userGroups = groupCache.getGroups( userDn.getNormName() ); 1130 Collection<ACITuple> tuples = new HashSet<ACITuple>(); 1131 addPerscriptiveAciTuples( opContext, tuples, normName, clonedEntry.getOriginalEntry() ); 1132 addEntryAciTuples( tuples, clonedEntry.getOriginalEntry() ); 1133 addSubentryAciTuples( opContext, tuples, normName, clonedEntry.getOriginalEntry() ); 1134 1135 if ( !engine.hasPermission( 1136 schemaManager, 1137 opContext, 1138 userGroups, 1139 userDn, 1140 principal.getAuthenticationLevel(), 1141 normName, 1142 null, 1143 null, 1144 SEARCH_ENTRY_PERMS, 1145 tuples, 1146 clonedEntry.getOriginalEntry(), 1147 null ) ) 1148 { 1149 return false; 1150 } 1151 1152 /* 1153 * For each attribute type we check if access is allowed to the type. If not 1154 * the attribute is yanked out of the entry to be returned. If permission is 1155 * allowed we move on to check if the values are allowed. Values that are 1156 * not allowed are removed from the attribute. If the attribute has no more 1157 * values remaining then the entire attribute is removed. 1158 */ 1159 List<AttributeType> attributeToRemove = new ArrayList<AttributeType>(); 1160 1161 for ( AttributeType attributeType:clonedEntry.getAttributeTypes() ) 1162 { 1163 // if attribute type scope access is not allowed then remove the attribute and continue 1164 String id = attributeType.getName(); 1165 EntryAttribute attr = clonedEntry.get( attributeType ); 1166 1167 if ( !engine.hasPermission( 1168 schemaManager, 1169 opContext, 1170 userGroups, 1171 userDn, 1172 principal.getAuthenticationLevel(), 1173 normName, 1174 id, 1175 null, 1176 SEARCH_ATTRVAL_PERMS, 1177 tuples, 1178 clonedEntry, 1179 null ) ) 1180 { 1181 attributeToRemove.add( attributeType ); 1182 1183 continue; 1184 } 1185 1186 List<Value<?>> valueToRemove = new ArrayList<Value<?>>(); 1187 1188 // attribute type scope is ok now let's determine value level scope 1189 for ( Value<?> value:attr ) 1190 { 1191 if ( !engine.hasPermission( 1192 schemaManager, 1193 opContext, 1194 userGroups, 1195 userDn, 1196 principal.getAuthenticationLevel(), 1197 normName, 1198 attr.getUpId(), 1199 value, 1200 SEARCH_ATTRVAL_PERMS, 1201 tuples, 1202 clonedEntry, 1203 null ) ) 1204 { 1205 valueToRemove.add( value ); 1206 } 1207 } 1208 1209 for ( Value<?> value:valueToRemove ) 1210 { 1211 attr.remove( value ); 1212 } 1213 1214 if ( attr.size() == 0 ) 1215 { 1216 attributeToRemove.add( attributeType ); 1217 } 1218 } 1219 1220 for ( AttributeType attributeType:attributeToRemove ) 1221 { 1222 clonedEntry.removeAttributes( attributeType ); 1223 } 1224 1225 return true; 1226 } 1227 1228 1229 /** 1230 * WARNING: create one of these filters fresh every time for each new search. 1231 */ 1232 class AuthorizationFilter implements EntryFilter 1233 { 1234 public boolean accept( SearchingOperationContext operationContext, ClonedServerEntry entry ) 1235 throws Exception 1236 { 1237 DN normName = entry.getDn().normalize( schemaManager.getNormalizerMapping() ); 1238 return filter( operationContext, normName, entry ); 1239 } 1240 } 1241 }