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.authz; 021 022 023import java.io.IOException; 024import java.text.ParseException; 025import java.util.ArrayList; 026import java.util.Collection; 027import java.util.Collections; 028import java.util.HashSet; 029import java.util.List; 030import java.util.Set; 031 032import javax.naming.directory.SearchControls; 033 034import org.apache.directory.api.ldap.aci.ACIItem; 035import org.apache.directory.api.ldap.aci.ACIItemParser; 036import org.apache.directory.api.ldap.aci.ACITuple; 037import org.apache.directory.api.ldap.aci.MicroOperation; 038import org.apache.directory.api.ldap.model.constants.Loggers; 039import org.apache.directory.api.ldap.model.constants.SchemaConstants; 040import org.apache.directory.api.ldap.model.entry.Attribute; 041import org.apache.directory.api.ldap.model.entry.Entry; 042import org.apache.directory.api.ldap.model.entry.Modification; 043import org.apache.directory.api.ldap.model.entry.Value; 044import org.apache.directory.api.ldap.model.exception.LdapException; 045import org.apache.directory.api.ldap.model.exception.LdapNoPermissionException; 046import org.apache.directory.api.ldap.model.exception.LdapOperationErrorException; 047import org.apache.directory.api.ldap.model.exception.LdapOperationException; 048import org.apache.directory.api.ldap.model.exception.LdapOtherException; 049import org.apache.directory.api.ldap.model.filter.EqualityNode; 050import org.apache.directory.api.ldap.model.filter.ExprNode; 051import org.apache.directory.api.ldap.model.filter.OrNode; 052import org.apache.directory.api.ldap.model.message.AliasDerefMode; 053import org.apache.directory.api.ldap.model.message.SearchScope; 054import org.apache.directory.api.ldap.model.name.Dn; 055import org.apache.directory.api.ldap.model.schema.AttributeType; 056import org.apache.directory.api.ldap.model.schema.normalizers.ConcreteNameComponentNormalizer; 057import org.apache.directory.server.constants.ServerDNConstants; 058import org.apache.directory.server.core.api.CoreSession; 059import org.apache.directory.server.core.api.DirectoryService; 060import org.apache.directory.server.core.api.InterceptorEnum; 061import org.apache.directory.server.core.api.LdapPrincipal; 062import org.apache.directory.server.core.api.entry.ClonedServerEntry; 063import org.apache.directory.server.core.api.entry.ServerEntryUtils; 064import org.apache.directory.server.core.api.filtering.EntryFilter; 065import org.apache.directory.server.core.api.filtering.EntryFilteringCursor; 066import org.apache.directory.server.core.api.interceptor.BaseInterceptor; 067import org.apache.directory.server.core.api.interceptor.context.AddOperationContext; 068import org.apache.directory.server.core.api.interceptor.context.CompareOperationContext; 069import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext; 070import org.apache.directory.server.core.api.interceptor.context.HasEntryOperationContext; 071import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext; 072import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext; 073import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext; 074import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext; 075import org.apache.directory.server.core.api.interceptor.context.OperationContext; 076import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext; 077import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext; 078import org.apache.directory.server.core.api.partition.Partition; 079import org.apache.directory.server.core.api.partition.PartitionNexus; 080import org.apache.directory.server.core.api.partition.PartitionTxn; 081import org.apache.directory.server.core.api.subtree.SubentryUtils; 082import org.apache.directory.server.core.authz.support.ACDFEngine; 083import org.apache.directory.server.core.authz.support.AciContext; 084import org.apache.directory.server.i18n.I18n; 085import org.slf4j.Logger; 086import org.slf4j.LoggerFactory; 087 088 089/** 090 * An ACI based authorization service. 091 * 092 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 093 */ 094public class AciAuthorizationInterceptor extends BaseInterceptor 095{ 096 /** the logger for this class */ 097 private static final Logger LOG = LoggerFactory.getLogger( AciAuthorizationInterceptor.class ); 098 099 /** the dedicated logger for ACI */ 100 private static final Logger ACI_LOG = LoggerFactory.getLogger( Loggers.ACI_LOG.getName() ); 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> BROWSE_PERMS; 109 private static final Collection<MicroOperation> LOOKUP_PERMS; 110 private static final Collection<MicroOperation> REPLACE_PERMS; 111 private static final Collection<MicroOperation> RENAME_PERMS; 112 private static final Collection<MicroOperation> EXPORT_PERMS; 113 private static final Collection<MicroOperation> IMPORT_PERMS; 114 private static final Collection<MicroOperation> MOVERENAME_PERMS; 115 116 static 117 { 118 Set<MicroOperation> set = new HashSet<>( 2 ); 119 set.add( MicroOperation.BROWSE ); 120 set.add( MicroOperation.RETURN_DN ); 121 SEARCH_ENTRY_PERMS = Collections.unmodifiableCollection( set ); 122 123 set = new HashSet<>( 2 ); 124 set.add( MicroOperation.READ ); 125 set.add( MicroOperation.BROWSE ); 126 LOOKUP_PERMS = Collections.unmodifiableCollection( set ); 127 128 set = new HashSet<>( 2 ); 129 set.add( MicroOperation.ADD ); 130 set.add( MicroOperation.REMOVE ); 131 REPLACE_PERMS = Collections.unmodifiableCollection( set ); 132 133 set = new HashSet<>( 2 ); 134 set.add( MicroOperation.EXPORT ); 135 set.add( MicroOperation.RENAME ); 136 MOVERENAME_PERMS = Collections.unmodifiableCollection( set ); 137 138 SEARCH_ATTRVAL_PERMS = Collections.singleton( MicroOperation.READ ); 139 ADD_PERMS = Collections.singleton( MicroOperation.ADD ); 140 READ_PERMS = Collections.singleton( MicroOperation.READ ); 141 COMPARE_PERMS = Collections.singleton( MicroOperation.COMPARE ); 142 REMOVE_PERMS = Collections.singleton( MicroOperation.REMOVE ); 143 BROWSE_PERMS = Collections.singleton( MicroOperation.BROWSE ); 144 RENAME_PERMS = Collections.singleton( MicroOperation.RENAME ); 145 EXPORT_PERMS = Collections.singleton( MicroOperation.EXPORT ); 146 IMPORT_PERMS = Collections.singleton( MicroOperation.IMPORT ); 147 } 148 149 /** a tupleCache that responds to add, delete, and modify attempts */ 150 private TupleCache tupleCache; 151 152 /** a groupCache that responds to add, delete, and modify attempts */ 153 private GroupCache groupCache; 154 155 /** a normalizing ACIItem parser */ 156 private ACIItemParser aciParser; 157 158 /** use and instance of the ACDF engine */ 159 private ACDFEngine engine; 160 161 /** the system wide subschemaSubentryDn */ 162 private Dn subschemaSubentryDn; 163 164 /** A reference to the nexus for direct backend operations */ 165 private PartitionNexus nexus; 166 167 public static final SearchControls DEFAULT_SEARCH_CONTROLS = new SearchControls(); 168 169 /** The SubentryUtils instance */ 170 private static SubentryUtils subentryUtils; 171 172 173 /** 174 * Create a AciAuthorizationInterceptor instance 175 */ 176 public AciAuthorizationInterceptor() 177 { 178 super( InterceptorEnum.ACI_AUTHORIZATION_INTERCEPTOR ); 179 } 180 181 182 /** 183 * Load the Tuples into the cache 184 */ 185 private void initTupleCache() throws LdapException 186 { 187 // Load all the prescriptiveACI : they are stored in AccessControlSubentry entries 188 SearchControls controls = new SearchControls(); 189 controls.setSearchScope( SearchControls.SUBTREE_SCOPE ); 190 controls.setReturningAttributes( new String[] 191 { SchemaConstants.PRESCRIPTIVE_ACI_AT } ); 192 193 AttributeType ocAt = directoryService.getAtProvider().getObjectClass(); 194 ExprNode filter = new EqualityNode<String>( ocAt, 195 new Value( ocAt, SchemaConstants.ACCESS_CONTROL_SUBENTRY_OC ) ); 196 197 CoreSession adminSession = directoryService.getAdminSession(); 198 199 SearchOperationContext searchOperationContext = new SearchOperationContext( adminSession, Dn.ROOT_DSE, filter, controls ); 200 201 searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES ); 202 Partition partition = nexus.getPartition( Dn.ROOT_DSE ); 203 searchOperationContext.setPartition( partition ); 204 205 try ( PartitionTxn partitionTxn = partition.beginReadTransaction() ) 206 { 207 searchOperationContext.setTransaction( partitionTxn ); 208 209 EntryFilteringCursor results = nexus.search( searchOperationContext ); 210 211 try 212 { 213 while ( results.next() ) 214 { 215 Entry entry = results.get(); 216 217 tupleCache.subentryAdded( entry.getDn(), entry ); 218 } 219 220 results.close(); 221 } 222 catch ( Exception e ) 223 { 224 throw new LdapOperationException( e.getMessage(), e ); 225 } 226 } 227 catch ( IOException ioe ) 228 { 229 throw new LdapOtherException( ioe.getMessage(), ioe ); 230 } 231 } 232 233 234 /** 235 * Load the Groups into the cache 236 */ 237 private void initGroupCache() throws LdapException 238 { 239 // Load all the member/uniqueMember : they are stored in groupOfNames/groupOfUniqueName 240 SearchControls controls = new SearchControls(); 241 controls.setSearchScope( SearchControls.SUBTREE_SCOPE ); 242 controls.setReturningAttributes( new String[] 243 { SchemaConstants.MEMBER_AT, SchemaConstants.UNIQUE_MEMBER_AT } ); 244 AttributeType ocAt = directoryService.getAtProvider().getObjectClass(); 245 246 ExprNode filter = 247 new OrNode( 248 new EqualityNode<String>( ocAt, new Value( ocAt, SchemaConstants.GROUP_OF_NAMES_OC ) ), 249 new EqualityNode<String>( ocAt, new Value( ocAt, SchemaConstants.GROUP_OF_UNIQUE_NAMES_OC ) ) ); 250 251 CoreSession adminSession = directoryService.getAdminSession(); 252 253 SearchOperationContext searchOperationContext = new SearchOperationContext( adminSession, Dn.ROOT_DSE, filter, 254 controls ); 255 256 searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES ); 257 258 EntryFilteringCursor results = nexus.search( searchOperationContext ); 259 260 try 261 { 262 while ( results.next() ) 263 { 264 Entry entry = results.get(); 265 266 groupCache.groupAdded( entry.getDn().getNormName(), entry ); 267 } 268 269 results.close(); 270 } 271 catch ( Exception e ) 272 { 273 throw new LdapOperationException( e.getMessage(), e ); 274 } 275 } 276 277 278 /** 279 * Initializes this interceptor based service by getting a handle on the nexus, setting up 280 * the tuple and group membership caches, the ACIItem parser and the ACDF engine. 281 * 282 * @param directoryService the directory service core 283 * @throws LdapException if there are problems during initialization 284 */ 285 @Override 286 public void init( DirectoryService directoryService ) throws LdapException 287 { 288 LOG.debug( "Initializing the AciAuthorizationInterceptor" ); 289 290 super.init( directoryService ); 291 292 nexus = directoryService.getPartitionNexus(); 293 294 CoreSession adminSession = directoryService.getAdminSession(); 295 296 // Create the caches 297 tupleCache = new TupleCache( adminSession ); 298 groupCache = new GroupCache( directoryService ); 299 300 // Iitialize the ACI PARSER and ACDF engine 301 aciParser = new ACIItemParser( new ConcreteNameComponentNormalizer( schemaManager ), schemaManager ); 302 engine = new ACDFEngine( schemaManager ); 303 304 // stuff for dealing with subentries (garbage for now) 305 Value subschemaSubentry = directoryService.getPartitionNexus().getRootDseValue( 306 directoryService.getAtProvider().getSubschemaSubentry() ); 307 subschemaSubentryDn = dnFactory.create( subschemaSubentry.getString() ); 308 309 // Init the caches now 310 initTupleCache(); 311 initGroupCache(); 312 313 // Init the SubentryUtils instance 314 subentryUtils = new SubentryUtils( directoryService ); 315 } 316 317 318 private void protectCriticalEntries( OperationContext opCtx, Dn dn ) throws LdapException 319 { 320 Dn principalDn = getPrincipal( opCtx ).getDn(); 321 322 if ( dn.isEmpty() ) 323 { 324 String msg = I18n.err( I18n.ERR_8 ); 325 LOG.error( msg ); 326 throw new LdapNoPermissionException( msg ); 327 } 328 329 if ( isTheAdministrator( dn ) ) 330 { 331 String msg = I18n.err( I18n.ERR_9, principalDn.getName(), dn.getName() ); 332 LOG.error( msg ); 333 throw new LdapNoPermissionException( msg ); 334 } 335 } 336 337 338 /** 339 * Adds perscriptiveACI tuples to a collection of tuples by accessing the 340 * tupleCache. The tuple cache is accessed for each A/C subentry 341 * associated with the protected entry. Note that subentries are handled 342 * differently: their parent, the administrative entry is accessed to 343 * determine the perscriptiveACIs effecting the AP and hence the subentry 344 * which is considered to be in the same context. 345 * 346 * @param tuples the collection of tuples to add to 347 * @param dn the normalized distinguished name of the protected entry 348 * @param entry the target entry whose access is being controlled 349 * @throws Exception if there are problems accessing attribute values 350 * @param proxy the partition nexus proxy object 351 */ 352 private void addPerscriptiveAciTuples( OperationContext opContext, Collection<ACITuple> tuples, Dn dn, Entry entry ) 353 throws LdapException 354 { 355 Entry originalEntry; 356 357 if ( entry instanceof ClonedServerEntry ) 358 { 359 originalEntry = ( ( ClonedServerEntry ) entry ).getOriginalEntry(); 360 } 361 else 362 { 363 originalEntry = entry; 364 } 365 366 Attribute oc = originalEntry.get( directoryService.getAtProvider().getObjectClass() ); 367 368 /* 369 * If the protected entry is a subentry, then the entry being evaluated 370 * for perscriptiveACIs is in fact the administrative entry. By 371 * substituting the administrative entry for the actual subentry the 372 * code below this "if" statement correctly evaluates the effects of 373 * perscriptiveACI on the subentry. Basically subentries are considered 374 * to be in the same naming context as their access point so the subentries 375 * effecting their parent entry applies to them as well. 376 */ 377 if ( oc.contains( SchemaConstants.SUBENTRY_OC ) ) 378 { 379 Dn parentDn = dn.getParent(); 380 CoreSession session = opContext.getSession(); 381 LookupOperationContext lookupContext = new LookupOperationContext( session, parentDn, 382 SchemaConstants.ALL_ATTRIBUTES_ARRAY ); 383 lookupContext.setPartition( opContext.getPartition() ); 384 lookupContext.setTransaction( opContext.getTransaction() ); 385 386 originalEntry = directoryService.getPartitionNexus().lookup( lookupContext ); 387 } 388 389 Attribute subentries = originalEntry.get( directoryService.getAtProvider().getAccessControlSubentries() ); 390 391 if ( subentries == null ) 392 { 393 return; 394 } 395 396 for ( Value value : subentries ) 397 { 398 String subentryDnStr = value.getString(); 399 Dn subentryDn = dnFactory.create( subentryDnStr ); 400 tuples.addAll( tupleCache.getACITuples( subentryDn.getNormName() ) ); 401 } 402 } 403 404 405 /** 406 * Adds the set of entryACI tuples to a collection of tuples. The entryACI 407 * is parsed and tuples are generated on they fly then added to the collection. 408 * 409 * @param tuples the collection of tuples to add to 410 * @param entry the target entry that access to is being regulated 411 * @throws Exception if there are problems accessing attribute values 412 */ 413 private void addEntryAciTuples( Collection<ACITuple> tuples, Entry entry ) throws LdapException 414 { 415 Attribute entryAci = entry.get( directoryService.getAtProvider().getEntryACI() ); 416 417 if ( entryAci == null ) 418 { 419 return; 420 } 421 422 for ( Value value : entryAci ) 423 { 424 String aciString = value.getString(); 425 ACIItem item; 426 427 try 428 { 429 item = aciParser.parse( aciString ); 430 } 431 catch ( ParseException e ) 432 { 433 String msg = I18n.err( I18n.ERR_10, aciString ); 434 LOG.error( msg, e ); 435 throw new LdapOperationErrorException( msg ); 436 } 437 438 tuples.addAll( item.toTuples() ); 439 } 440 } 441 442 443 /** 444 * Adds the set of subentryACI tuples to a collection of tuples. The subentryACI 445 * is parsed and tuples are generated on the fly then added to the collection. 446 * 447 * @param tuples the collection of tuples to add to 448 * @param dn the normalized distinguished name of the protected entry 449 * @param entry the target entry that access to is being regulated 450 * @throws Exception if there are problems accessing attribute values 451 * @param proxy the partition nexus proxy object 452 */ 453 private void addSubentryAciTuples( OperationContext opContext, Collection<ACITuple> tuples, Dn dn, Entry entry ) 454 throws LdapException 455 { 456 // only perform this for subentries 457 if ( !entry.contains( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.SUBENTRY_OC ) ) 458 { 459 return; 460 } 461 462 // get the parent or administrative entry for this subentry since it 463 // will contain the subentryACI attributes that effect subentries 464 Dn parentDn = dn.getParent(); 465 466 CoreSession session = opContext.getSession(); 467 LookupOperationContext lookupContext = new LookupOperationContext( session, parentDn, 468 SchemaConstants.ALL_ATTRIBUTES_ARRAY ); 469 lookupContext.setPartition( opContext.getPartition() ); 470 lookupContext.setTransaction( opContext.getTransaction() ); 471 472 Entry administrativeEntry = ( ( ClonedServerEntry ) directoryService.getPartitionNexus().lookup( lookupContext ) ) 473 .getOriginalEntry(); 474 475 Attribute subentryAci = administrativeEntry.get( directoryService.getAtProvider().getSubentryACI() ); 476 477 if ( subentryAci == null ) 478 { 479 return; 480 } 481 482 for ( Value value : subentryAci ) 483 { 484 String aciString = value.getString(); 485 ACIItem item; 486 487 try 488 { 489 item = aciParser.parse( aciString ); 490 } 491 catch ( ParseException e ) 492 { 493 String msg = I18n.err( I18n.ERR_11, aciString ); 494 LOG.error( msg, e ); 495 throw new LdapOperationErrorException( msg ); 496 } 497 498 tuples.addAll( item.toTuples() ); 499 } 500 } 501 502 503 /* ------------------------------------------------------------------------------- 504 * Within every access controled interceptor method we must retrieve the ACITuple 505 * set for all the perscriptiveACIs that apply to the candidate, the target entry 506 * operated upon. This ACITuple set is gotten from the TupleCache by looking up 507 * the subentries referenced by the accessControlSubentries operational attribute 508 * within the target entry. 509 * 510 * Then the entry is inspected for an entryACI. This is not done for the add op 511 * since it could introduce a security breech. So for non-add ops if present a 512 * set of ACITuples are generated for all the entryACIs within the entry. This 513 * set is combined with the ACITuples cached for the perscriptiveACI affecting 514 * the target entry. If the entry is a subentry the ACIs are also processed for 515 * the subentry to generate more ACITuples. This subentry TupleACI set is joined 516 * with the entry and perscriptive ACI. 517 * 518 * The union of ACITuples are fed into the engine along with other parameters 519 * to decide whether a permission is granted or rejected for the specific 520 * operation. 521 * ------------------------------------------------------------------------------- 522 */ 523 /** 524 * {@inheritDoc} 525 */ 526 @Override 527 public void add( AddOperationContext addContext ) throws LdapException 528 { 529 // bypass authz code if it was disabled 530 if ( !directoryService.isAccessControlEnabled() ) 531 { 532 ACI_LOG.debug( "ACI interceptor disabled" ); 533 next( addContext ); 534 return; 535 } 536 537 ACI_LOG.debug( "Adding the entry {}", addContext.getEntry() ); 538 539 // Access the principal requesting the operation, and bypass checks if it is the admin 540 LdapPrincipal principal = addContext.getSession().getEffectivePrincipal(); 541 Dn principalDn = principal.getDn(); 542 543 Entry serverEntry = addContext.getEntry(); 544 545 Dn dn = addContext.getDn(); 546 547 // bypass authz code but manage caches if operation is performed by the admin 548 if ( isPrincipalAnAdministrator( principalDn ) ) 549 { 550 ACI_LOG.debug( "Addition done by the administartor : no check" ); 551 552 next( addContext ); 553 tupleCache.subentryAdded( dn, serverEntry ); 554 groupCache.groupAdded( dn.getNormName(), serverEntry ); 555 return; 556 } 557 558 // perform checks below here for all non-admin users 559 Entry subentry = subentryUtils.getSubentryAttributes( dn, serverEntry ); 560 561 for ( Attribute attribute : serverEntry ) 562 { 563 subentry.put( attribute ); 564 } 565 566 // Assemble all the information required to make an access control decision 567 Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() ); 568 Collection<ACITuple> tuples = new HashSet<>(); 569 570 // Build the total collection of tuples to be considered for add rights 571 // NOTE: entryACI are NOT considered in adds (it would be a security breech) 572 addPerscriptiveAciTuples( addContext, tuples, dn, subentry ); 573 addSubentryAciTuples( addContext, tuples, dn, subentry ); 574 575 // check if entry scope permission is granted 576 AciContext entryAciCtx = new AciContext( schemaManager, addContext ); 577 entryAciCtx.setUserGroupNames( userGroups ); 578 entryAciCtx.setUserDn( principalDn ); 579 entryAciCtx.setAuthenticationLevel( principal.getAuthenticationLevel() ); 580 entryAciCtx.setEntryDn( dn ); 581 entryAciCtx.setMicroOperations( ADD_PERMS ); 582 entryAciCtx.setAciTuples( tuples ); 583 entryAciCtx.setEntry( subentry ); 584 585 engine.checkPermission( entryAciCtx ); 586 587 // now we must check if attribute type and value scope permission is granted 588 for ( Attribute attribute : serverEntry ) 589 { 590 for ( Value value : attribute ) 591 { 592 AciContext attrAciContext = new AciContext( schemaManager, addContext ); 593 attrAciContext.setUserGroupNames( userGroups ); 594 attrAciContext.setUserDn( principalDn ); 595 attrAciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); 596 attrAciContext.setEntryDn( dn ); 597 attrAciContext.setAttributeType( attribute.getAttributeType() ); 598 attrAciContext.setAttrValue( value ); 599 attrAciContext.setMicroOperations( ADD_PERMS ); 600 attrAciContext.setAciTuples( tuples ); 601 attrAciContext.setEntry( serverEntry ); 602 603 engine.checkPermission( attrAciContext ); 604 } 605 } 606 607 // if we've gotten this far then access has been granted 608 next( addContext ); 609 610 // if the entry added is a subentry or a groupOf[Unique]Names we must 611 // update the ACITuple cache and the groups cache to keep them in sync 612 tupleCache.subentryAdded( dn, serverEntry ); 613 groupCache.groupAdded( dn.getNormName(), serverEntry ); 614 } 615 616 617 /** 618 * {@inheritDoc} 619 */ 620 @Override 621 public boolean compare( CompareOperationContext compareContext ) throws LdapException 622 { 623 CoreSession session = compareContext.getSession(); 624 Dn dn = compareContext.getDn(); 625 String oid = compareContext.getOid(); 626 627 Entry entry = compareContext.getOriginalEntry(); 628 629 LdapPrincipal principal = session.getEffectivePrincipal(); 630 Dn principalDn = principal.getDn(); 631 632 if ( isPrincipalAnAdministrator( principalDn ) || !directoryService.isAccessControlEnabled() ) 633 { 634 return next( compareContext ); 635 } 636 637 Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() ); 638 Collection<ACITuple> tuples = new HashSet<>(); 639 addPerscriptiveAciTuples( compareContext, tuples, dn, entry ); 640 addEntryAciTuples( tuples, entry ); 641 addSubentryAciTuples( compareContext, tuples, dn, entry ); 642 643 AciContext aciContext = new AciContext( schemaManager, compareContext ); 644 aciContext.setUserGroupNames( userGroups ); 645 aciContext.setUserDn( principalDn ); 646 aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); 647 aciContext.setEntryDn( dn ); 648 aciContext.setMicroOperations( READ_PERMS ); 649 aciContext.setAciTuples( tuples ); 650 aciContext.setEntry( entry ); 651 652 engine.checkPermission( aciContext ); 653 654 AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( oid ); 655 656 aciContext = new AciContext( schemaManager, compareContext ); 657 aciContext.setUserGroupNames( userGroups ); 658 aciContext.setUserDn( principalDn ); 659 aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); 660 aciContext.setEntryDn( dn ); 661 aciContext.setAttributeType( attributeType ); 662 aciContext.setMicroOperations( COMPARE_PERMS ); 663 aciContext.setAciTuples( tuples ); 664 aciContext.setEntry( entry ); 665 666 engine.checkPermission( aciContext ); 667 668 return next( compareContext ); 669 } 670 671 672 /** 673 * {@inheritDoc} 674 */ 675 @Override 676 public void delete( DeleteOperationContext deleteContext ) throws LdapException 677 { 678 CoreSession session = deleteContext.getSession(); 679 680 // bypass authz code if we are disabled 681 if ( !directoryService.isAccessControlEnabled() ) 682 { 683 next( deleteContext ); 684 return; 685 } 686 687 Dn dn = deleteContext.getDn(); 688 LdapPrincipal principal = session.getEffectivePrincipal(); 689 Dn principalDn = principal.getDn(); 690 691 Entry entry = deleteContext.getEntry(); 692 693 protectCriticalEntries( deleteContext, dn ); 694 695 // bypass authz code but manage caches if operation is performed by the admin 696 if ( isPrincipalAnAdministrator( principalDn ) ) 697 { 698 next( deleteContext ); 699 700 tupleCache.subentryDeleted( dn, entry ); 701 groupCache.groupDeleted( dn, entry ); 702 703 return; 704 } 705 706 Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() ); 707 Collection<ACITuple> tuples = new HashSet<>(); 708 addPerscriptiveAciTuples( deleteContext, tuples, dn, entry ); 709 addEntryAciTuples( tuples, entry ); 710 addSubentryAciTuples( deleteContext, tuples, dn, entry ); 711 712 AciContext aciContext = new AciContext( schemaManager, deleteContext ); 713 aciContext.setUserGroupNames( userGroups ); 714 aciContext.setUserDn( principalDn ); 715 aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); 716 aciContext.setEntryDn( dn ); 717 aciContext.setMicroOperations( REMOVE_PERMS ); 718 aciContext.setAciTuples( tuples ); 719 aciContext.setEntry( entry ); 720 721 engine.checkPermission( aciContext ); 722 723 next( deleteContext ); 724 725 tupleCache.subentryDeleted( dn, entry ); 726 groupCache.groupDeleted( dn, entry ); 727 } 728 729 730 /** 731 * {@inheritDoc} 732 */ 733 @Override 734 public boolean hasEntry( HasEntryOperationContext hasEntryContext ) throws LdapException 735 { 736 Dn dn = hasEntryContext.getDn(); 737 738 if ( !directoryService.isAccessControlEnabled() ) 739 { 740 return dn.isRootDse() || next( hasEntryContext ); 741 } 742 743 boolean answer = next( hasEntryContext ); 744 745 // no checks on the RootDSE 746 if ( dn.isRootDse() ) 747 { 748 // No need to go down to the stack, if the dn is empty 749 // It's the rootDSE, and it exists ! 750 return answer; 751 } 752 753 CoreSession session = hasEntryContext.getSession(); 754 755 // TODO - eventually replace this with a check on session.isAnAdministrator() 756 LdapPrincipal principal = session.getEffectivePrincipal(); 757 Dn principalDn = principal.getDn(); 758 759 if ( isPrincipalAnAdministrator( principalDn ) ) 760 { 761 return answer; 762 } 763 764 LookupOperationContext lookupContext = new LookupOperationContext( session, dn, 765 SchemaConstants.ALL_ATTRIBUTES_ARRAY ); 766 lookupContext.setPartition( hasEntryContext.getPartition() ); 767 lookupContext.setTransaction( hasEntryContext.getTransaction() ); 768 769 Entry entry = directoryService.getPartitionNexus().lookup( lookupContext ); 770 771 Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() ); 772 Collection<ACITuple> tuples = new HashSet<>(); 773 addPerscriptiveAciTuples( hasEntryContext, tuples, dn, entry ); 774 addEntryAciTuples( tuples, ( ( ClonedServerEntry ) entry ).getOriginalEntry() ); 775 addSubentryAciTuples( hasEntryContext, tuples, dn, ( ( ClonedServerEntry ) entry ).getOriginalEntry() ); 776 777 // check that we have browse access to the entry 778 AciContext aciContext = new AciContext( schemaManager, hasEntryContext ); 779 aciContext.setUserGroupNames( userGroups ); 780 aciContext.setUserDn( principalDn ); 781 aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); 782 aciContext.setEntryDn( dn ); 783 aciContext.setMicroOperations( BROWSE_PERMS ); 784 aciContext.setAciTuples( tuples ); 785 aciContext.setEntry( ( ( ClonedServerEntry ) entry ).getOriginalEntry() ); 786 787 engine.checkPermission( aciContext ); 788 789 return next( hasEntryContext ); 790 } 791 792 793 /** 794 * {@inheritDoc} 795 */ 796 @Override 797 public Entry lookup( LookupOperationContext lookupContext ) throws LdapException 798 { 799 CoreSession session = lookupContext.getSession(); 800 801 Entry entry = next( lookupContext ); 802 803 LdapPrincipal principal = session.getEffectivePrincipal(); 804 Dn principalDn = principal.getDn(); 805 806 if ( !principalDn.isSchemaAware() ) 807 { 808 principalDn = new Dn( schemaManager, principalDn ); 809 } 810 811 // Bypass this interceptor if we disabled the AC subsystem or if the principal is the admin 812 if ( isPrincipalAnAdministrator( principalDn ) || !directoryService.isAccessControlEnabled() ) 813 { 814 return entry; 815 } 816 817 checkLookupAccess( lookupContext, entry ); 818 819 return entry; 820 } 821 822 823 /** 824 * {@inheritDoc} 825 */ 826 @Override 827 public void modify( ModifyOperationContext modifyContext ) throws LdapException 828 { 829 Dn dn = modifyContext.getDn(); 830 831 // Access the principal requesting the operation, and bypass checks if it is the admin 832 Entry entry = modifyContext.getEntry(); 833 834 LdapPrincipal principal = modifyContext.getSession().getEffectivePrincipal(); 835 Dn principalDn = principal.getDn(); 836 837 // bypass authz code if we are disabled 838 if ( !directoryService.isAccessControlEnabled() ) 839 { 840 next( modifyContext ); 841 return; 842 } 843 844 List<Modification> mods = modifyContext.getModItems(); 845 846 // bypass authz code but manage caches if operation is performed by the admin 847 if ( isPrincipalAnAdministrator( principalDn ) ) 848 { 849 next( modifyContext ); 850 851 Entry modifiedEntry = modifyContext.getAlteredEntry(); 852 tupleCache.subentryModified( dn, mods, modifiedEntry ); 853 groupCache.groupModified( dn, mods, entry, schemaManager ); 854 855 return; 856 } 857 858 Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() ); 859 Collection<ACITuple> tuples = new HashSet<>(); 860 addPerscriptiveAciTuples( modifyContext, tuples, dn, entry ); 861 addEntryAciTuples( tuples, entry ); 862 addSubentryAciTuples( modifyContext, tuples, dn, entry ); 863 864 AciContext entryAciContext = new AciContext( schemaManager, modifyContext ); 865 entryAciContext.setUserGroupNames( userGroups ); 866 entryAciContext.setUserDn( principalDn ); 867 entryAciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); 868 entryAciContext.setEntryDn( dn ); 869 entryAciContext.setMicroOperations( Collections.singleton( MicroOperation.MODIFY ) ); 870 entryAciContext.setAciTuples( tuples ); 871 entryAciContext.setEntry( entry ); 872 873 engine.checkPermission( entryAciContext ); 874 875 Collection<MicroOperation> perms; 876 Entry entryView = entry.clone(); 877 878 for ( Modification mod : mods ) 879 { 880 Attribute attr = mod.getAttribute(); 881 882 switch ( mod.getOperation() ) 883 { 884 case ADD_ATTRIBUTE: 885 perms = ADD_PERMS; 886 887 // If the attribute is being created with an initial value ... 888 if ( entry.get( attr.getId() ) == null ) 889 { 890 AciContext attrAciContext = new AciContext( schemaManager, modifyContext ); 891 attrAciContext.setUserGroupNames( userGroups ); 892 attrAciContext.setUserDn( principalDn ); 893 attrAciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); 894 attrAciContext.setEntryDn( dn ); 895 attrAciContext.setAttributeType( attr.getAttributeType() ); 896 attrAciContext.setMicroOperations( perms ); 897 attrAciContext.setAciTuples( tuples ); 898 attrAciContext.setEntry( entry ); 899 900 // ... we also need to check if adding the attribute is permitted 901 engine.checkPermission( attrAciContext ); 902 } 903 904 break; 905 906 case REMOVE_ATTRIBUTE: 907 perms = REMOVE_PERMS; 908 Attribute entryAttr = entry.get( attr.getId() ); 909 910 if ( ( entryAttr != null ) && ( entryAttr.size() == 1 ) ) 911 { 912 // If there is only one value remaining in the attribute ... 913 // ... we also need to check if removing the attribute at all is permitted 914 AciContext aciContext = new AciContext( schemaManager, modifyContext ); 915 aciContext.setUserGroupNames( userGroups ); 916 aciContext.setUserDn( principalDn ); 917 aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); 918 aciContext.setEntryDn( dn ); 919 aciContext.setAttributeType( attr.getAttributeType() ); 920 aciContext.setMicroOperations( perms ); 921 aciContext.setAciTuples( tuples ); 922 aciContext.setEntry( entry ); 923 924 engine.checkPermission( aciContext ); 925 } 926 927 break; 928 929 case REPLACE_ATTRIBUTE: 930 perms = REPLACE_PERMS; 931 break; 932 933 default: 934 throw new IllegalArgumentException( "Unexpected modify operation " + mod.getOperation() ); 935 } 936 937 /** 938 * Update the entry view as the current modification is applied to the original entry. 939 * This is especially required for handling the MaxValueCount protected item. Number of 940 * values for an attribute after a modification should be known in advance in order to 941 * check permissions for MaxValueCount protected item. So during addition of the first 942 * value of an attribute it can be rejected if the permission denied due the the 943 * MaxValueCount protected item. This is not the perfect implementation as required by 944 * the specification because the system should reject the addition exactly on the right 945 * value of the attribute. However as we do not have that much granularity in our 946 * implementation (we consider an Attribute Addition itself a Micro Operation, 947 * not the individual Value Additions) we just handle this when the first value of an 948 * attribute is being checked for relevant permissions below. 949 */ 950 entryView = ServerEntryUtils.getTargetEntry( mod, entryView, schemaManager ); 951 952 for ( Value value : attr ) 953 { 954 AciContext aciContext = new AciContext( schemaManager, modifyContext ); 955 aciContext.setUserGroupNames( userGroups ); 956 aciContext.setUserDn( principalDn ); 957 aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); 958 aciContext.setEntryDn( dn ); 959 aciContext.setAttributeType( attr.getAttributeType() ); 960 aciContext.setAttrValue( value ); 961 aciContext.setMicroOperations( perms ); 962 aciContext.setAciTuples( tuples ); 963 aciContext.setEntry( entry ); 964 aciContext.setEntryView( entryView ); 965 966 engine.checkPermission( aciContext ); 967 } 968 } 969 970 next( modifyContext ); 971 972 Entry modifiedEntry = modifyContext.getAlteredEntry(); 973 tupleCache.subentryModified( dn, mods, modifiedEntry ); 974 groupCache.groupModified( dn, mods, entry, schemaManager ); 975 } 976 977 978 /** 979 * {@inheritDoc} 980 */ 981 @Override 982 public void move( MoveOperationContext moveContext ) throws LdapException 983 { 984 Dn oriChildName = moveContext.getDn(); 985 986 // Access the principal requesting the operation, and bypass checks if it is the admin 987 Entry entry = moveContext.getOriginalEntry(); 988 CoreSession session = moveContext.getSession(); 989 990 Dn newDn = moveContext.getNewDn(); 991 992 LdapPrincipal principal = session.getEffectivePrincipal(); 993 Dn principalDn = principal.getDn(); 994 995 // bypass authz code if we are disabled 996 if ( !directoryService.isAccessControlEnabled() ) 997 { 998 next( moveContext ); 999 return; 1000 } 1001 1002 protectCriticalEntries( moveContext, oriChildName ); 1003 1004 // bypass authz code but manage caches if operation is performed by the admin 1005 if ( isPrincipalAnAdministrator( principalDn ) ) 1006 { 1007 next( moveContext ); 1008 tupleCache.subentryRenamed( oriChildName, newDn ); 1009 groupCache.groupRenamed( oriChildName, newDn ); 1010 return; 1011 } 1012 1013 Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() ); 1014 Collection<ACITuple> tuples = new HashSet<>(); 1015 addPerscriptiveAciTuples( moveContext, tuples, oriChildName, entry ); 1016 addEntryAciTuples( tuples, entry ); 1017 addSubentryAciTuples( moveContext, tuples, oriChildName, entry ); 1018 1019 AciContext aciContext = new AciContext( schemaManager, moveContext ); 1020 aciContext.setUserGroupNames( userGroups ); 1021 aciContext.setUserDn( principalDn ); 1022 aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); 1023 aciContext.setEntryDn( oriChildName ); 1024 aciContext.setMicroOperations( EXPORT_PERMS ); 1025 aciContext.setAciTuples( tuples ); 1026 aciContext.setEntry( entry ); 1027 1028 engine.checkPermission( aciContext ); 1029 1030 // Get the entry again without operational attributes 1031 // because access control subentry operational attributes 1032 // will not be valid at the new location. 1033 // This will certainly be fixed by the SubentryInterceptor, 1034 // but after this service. 1035 LookupOperationContext lookupContext = new LookupOperationContext( session, oriChildName, 1036 SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY ); 1037 lookupContext.setPartition( moveContext.getPartition() ); 1038 lookupContext.setTransaction( moveContext.getTransaction() ); 1039 1040 Entry importedEntry = directoryService.getPartitionNexus().lookup( lookupContext ); 1041 1042 // As the target entry does not exist yet and so 1043 // its subentry operational attributes are not there, 1044 // we need to construct an entry to represent it 1045 // at least with minimal requirements which are object class 1046 // and access control subentry operational attributes. 1047 Entry subentryAttrs = subentryUtils.getSubentryAttributes( newDn, importedEntry ); 1048 1049 for ( Attribute attribute : importedEntry ) 1050 { 1051 subentryAttrs.put( attribute ); 1052 } 1053 1054 Collection<ACITuple> destTuples = new HashSet<>(); 1055 // Import permission is only valid for prescriptive ACIs 1056 addPerscriptiveAciTuples( moveContext, destTuples, newDn, subentryAttrs ); 1057 1058 // Evaluate the target context to see whether it 1059 // allows an entry named newName to be imported as a subordinate. 1060 aciContext = new AciContext( schemaManager, moveContext ); 1061 aciContext.setUserGroupNames( userGroups ); 1062 aciContext.setUserDn( principalDn ); 1063 aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); 1064 aciContext.setEntryDn( newDn ); 1065 aciContext.setMicroOperations( IMPORT_PERMS ); 1066 aciContext.setAciTuples( destTuples ); 1067 aciContext.setEntry( subentryAttrs ); 1068 1069 engine.checkPermission( aciContext ); 1070 1071 next( moveContext ); 1072 tupleCache.subentryRenamed( oriChildName, newDn ); 1073 groupCache.groupRenamed( oriChildName, newDn ); 1074 } 1075 1076 1077 /** 1078 * {@inheritDoc} 1079 */ 1080 @Override 1081 public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException 1082 { 1083 Dn oldDn = moveAndRenameContext.getDn(); 1084 CoreSession session = moveAndRenameContext.getSession(); 1085 1086 Entry entry = moveAndRenameContext.getOriginalEntry(); 1087 1088 LdapPrincipal principal = session.getEffectivePrincipal(); 1089 Dn principalDn = principal.getDn(); 1090 Dn newDn = moveAndRenameContext.getNewDn(); 1091 1092 // bypass authz code if we are disabled 1093 if ( !directoryService.isAccessControlEnabled() ) 1094 { 1095 next( moveAndRenameContext ); 1096 1097 return; 1098 } 1099 1100 protectCriticalEntries( moveAndRenameContext, oldDn ); 1101 1102 // bypass authz code but manage caches if operation is performed by the admin 1103 if ( isPrincipalAnAdministrator( principalDn ) ) 1104 { 1105 next( moveAndRenameContext ); 1106 tupleCache.subentryRenamed( oldDn, newDn ); 1107 groupCache.groupRenamed( oldDn, newDn ); 1108 1109 return; 1110 } 1111 1112 Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() ); 1113 Collection<ACITuple> tuples = new HashSet<>(); 1114 addPerscriptiveAciTuples( moveAndRenameContext, tuples, oldDn, entry ); 1115 addEntryAciTuples( tuples, entry ); 1116 addSubentryAciTuples( moveAndRenameContext, tuples, oldDn, entry ); 1117 1118 AciContext aciContext = new AciContext( schemaManager, moveAndRenameContext ); 1119 aciContext.setUserGroupNames( userGroups ); 1120 aciContext.setUserDn( principalDn ); 1121 aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); 1122 aciContext.setEntryDn( oldDn ); 1123 aciContext.setMicroOperations( MOVERENAME_PERMS ); 1124 aciContext.setAciTuples( tuples ); 1125 aciContext.setEntry( entry ); 1126 1127 engine.checkPermission( aciContext ); 1128 1129 // Get the entry again without operational attributes 1130 // because access control subentry operational attributes 1131 // will not be valid at the new location. 1132 // This will certainly be fixed by the SubentryInterceptor, 1133 // but after this service. 1134 1135 LookupOperationContext lookupContext = new LookupOperationContext( session, oldDn, 1136 SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY ); 1137 lookupContext.setPartition( moveAndRenameContext.getPartition() ); 1138 lookupContext.setTransaction( moveAndRenameContext.getTransaction() ); 1139 1140 Entry importedEntry = directoryService.getPartitionNexus().lookup( lookupContext ); 1141 1142 // As the target entry does not exist yet and so 1143 // its subentry operational attributes are not there, 1144 // we need to construct an entry to represent it 1145 // at least with minimal requirements which are object class 1146 // and access control subentry operational attributes. 1147 Entry subentryAttrs = subentryUtils.getSubentryAttributes( newDn, importedEntry ); 1148 1149 for ( Attribute attribute : importedEntry ) 1150 { 1151 subentryAttrs.put( attribute ); 1152 } 1153 1154 Collection<ACITuple> destTuples = new HashSet<>(); 1155 // Import permission is only valid for prescriptive ACIs 1156 addPerscriptiveAciTuples( moveAndRenameContext, destTuples, newDn, subentryAttrs ); 1157 1158 // Evaluate the target context to see whether it 1159 // allows an entry named newName to be imported as a subordinate. 1160 aciContext = new AciContext( schemaManager, moveAndRenameContext ); 1161 aciContext.setUserGroupNames( userGroups ); 1162 aciContext.setUserDn( principalDn ); 1163 aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); 1164 aciContext.setEntryDn( newDn ); 1165 aciContext.setMicroOperations( IMPORT_PERMS ); 1166 aciContext.setAciTuples( destTuples ); 1167 aciContext.setEntry( subentryAttrs ); 1168 1169 engine.checkPermission( aciContext ); 1170 1171 next( moveAndRenameContext ); 1172 tupleCache.subentryRenamed( oldDn, newDn ); 1173 groupCache.groupRenamed( oldDn, newDn ); 1174 } 1175 1176 1177 /** 1178 * {@inheritDoc} 1179 */ 1180 @Override 1181 public void rename( RenameOperationContext renameContext ) throws LdapException 1182 { 1183 Dn oldName = renameContext.getDn(); 1184 Entry originalEntry = renameContext.getOriginalEntry(); 1185 1186 if ( renameContext.getEntry() != null ) 1187 { 1188 originalEntry = ( ( ClonedServerEntry ) renameContext.getEntry() ).getOriginalEntry(); 1189 } 1190 1191 LdapPrincipal principal = renameContext.getSession().getEffectivePrincipal(); 1192 Dn principalDn = principal.getDn(); 1193 Dn newName = renameContext.getNewDn(); 1194 1195 // bypass authz code if we are disabled 1196 if ( !directoryService.isAccessControlEnabled() ) 1197 { 1198 next( renameContext ); 1199 return; 1200 } 1201 1202 protectCriticalEntries( renameContext, oldName ); 1203 1204 // bypass authz code but manage caches if operation is performed by the admin 1205 if ( isPrincipalAnAdministrator( principalDn ) ) 1206 { 1207 next( renameContext ); 1208 tupleCache.subentryRenamed( oldName, newName ); 1209 1210 // TODO : this method returns a boolean : what should we do with the result ? 1211 groupCache.groupRenamed( oldName, newName ); 1212 1213 return; 1214 } 1215 1216 Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() ); 1217 Collection<ACITuple> tuples = new HashSet<>(); 1218 addPerscriptiveAciTuples( renameContext, tuples, oldName, originalEntry ); 1219 addEntryAciTuples( tuples, originalEntry ); 1220 addSubentryAciTuples( renameContext, tuples, oldName, originalEntry ); 1221 1222 AciContext aciContext = new AciContext( schemaManager, renameContext ); 1223 aciContext.setUserGroupNames( userGroups ); 1224 aciContext.setUserDn( principalDn ); 1225 aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); 1226 aciContext.setEntryDn( oldName ); 1227 aciContext.setMicroOperations( RENAME_PERMS ); 1228 aciContext.setAciTuples( tuples ); 1229 aciContext.setEntry( originalEntry ); 1230 1231 engine.checkPermission( aciContext ); 1232 1233 next( renameContext ); 1234 tupleCache.subentryRenamed( oldName, newName ); 1235 groupCache.groupRenamed( oldName, newName ); 1236 } 1237 1238 1239 /** 1240 * {@inheritDoc} 1241 */ 1242 @Override 1243 public EntryFilteringCursor search( SearchOperationContext searchContext ) throws LdapException 1244 { 1245 LdapPrincipal user = searchContext.getSession().getEffectivePrincipal(); 1246 Dn principalDn = user.getDn(); 1247 EntryFilteringCursor cursor = next( searchContext ); 1248 1249 boolean isSubschemaSubentryLookup = subschemaSubentryDn.equals( searchContext.getDn() ); 1250 1251 boolean isRootDseLookup = ( searchContext.getDn().size() == 0 ) 1252 && ( searchContext.getScope() == SearchScope.OBJECT ); 1253 1254 if ( isPrincipalAnAdministrator( principalDn ) 1255 || !directoryService.isAccessControlEnabled() || isRootDseLookup 1256 || isSubschemaSubentryLookup ) 1257 { 1258 return cursor; 1259 } 1260 1261 cursor.addEntryFilter( new AuthorizationFilter() ); 1262 return cursor; 1263 } 1264 1265 1266 /** 1267 * Checks if the READ permissions exist to the entry and to each attribute type and 1268 * value. 1269 * 1270 * @todo not sure if we should hide attribute types/values or throw an exception 1271 * instead. I think we're going to have to use a filter to restrict the return 1272 * of attribute types and values instead of throwing an exception. Lack of read 1273 * perms to attributes and their values results in their removal when returning 1274 * the entry. 1275 * 1276 * @param principal the user associated with the call 1277 * @param dn the name of the entry being looked up 1278 * @param entry the raw entry pulled from the nexus 1279 * @throws Exception if undlying access to the DIT fails 1280 */ 1281 private void checkLookupAccess( LookupOperationContext lookupContext, Entry entry ) throws LdapException 1282 { 1283 Dn dn = lookupContext.getDn(); 1284 1285 // no permissions checks on the RootDSE 1286 if ( dn.isRootDse() ) 1287 { 1288 return; 1289 } 1290 1291 LdapPrincipal principal = lookupContext.getSession().getEffectivePrincipal(); 1292 Dn userName = principal.getDn(); 1293 Set<String> userGroups = groupCache.getGroups( userName.getNormName() ); 1294 Collection<ACITuple> tuples = new HashSet<>(); 1295 addPerscriptiveAciTuples( lookupContext, tuples, dn, entry ); 1296 addEntryAciTuples( tuples, entry ); 1297 addSubentryAciTuples( lookupContext, tuples, dn, entry ); 1298 1299 // check that we have read access to the entry 1300 AciContext aciContext = new AciContext( schemaManager, lookupContext ); 1301 aciContext.setUserGroupNames( userGroups ); 1302 aciContext.setUserDn( userName ); 1303 aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); 1304 aciContext.setEntryDn( dn ); 1305 aciContext.setMicroOperations( LOOKUP_PERMS ); 1306 aciContext.setAciTuples( tuples ); 1307 aciContext.setEntry( entry ); 1308 1309 engine.checkPermission( aciContext ); 1310 1311 // check that we have read access to every attribute type and value 1312 for ( Attribute attribute : entry ) 1313 { 1314 1315 for ( Value value : attribute ) 1316 { 1317 AciContext valueAciContext = new AciContext( schemaManager, lookupContext ); 1318 valueAciContext.setUserGroupNames( userGroups ); 1319 valueAciContext.setUserDn( userName ); 1320 valueAciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); 1321 valueAciContext.setEntryDn( dn ); 1322 valueAciContext.setAttributeType( attribute.getAttributeType() ); 1323 valueAciContext.setAttrValue( value ); 1324 valueAciContext.setMicroOperations( READ_PERMS ); 1325 valueAciContext.setAciTuples( tuples ); 1326 valueAciContext.setEntry( entry ); 1327 1328 engine.checkPermission( valueAciContext ); 1329 } 1330 } 1331 } 1332 1333 1334 public final boolean isPrincipalAnAdministrator( Dn principalDn ) 1335 { 1336 return groupCache.isPrincipalAnAdministrator( principalDn.getNormName() ); 1337 } 1338 1339 1340 public void cacheNewGroup( String name, Entry entry ) throws LdapException 1341 { 1342 groupCache.groupAdded( name, entry ); 1343 } 1344 1345 1346 private boolean filter( OperationContext opContext, Dn normName, Entry clonedEntry ) throws LdapException 1347 { 1348 /* 1349 * First call hasPermission() for entry level "Browse" and "ReturnDN" perm 1350 * tests. If we hasPermission() returns false we immediately short the 1351 * process and return false. 1352 */ 1353 1354 LdapPrincipal principal = opContext.getSession().getEffectivePrincipal(); 1355 Dn userDn = principal.getDn(); 1356 Set<String> userGroups = groupCache.getGroups( userDn.getNormName() ); 1357 Collection<ACITuple> tuples = new HashSet<>(); 1358 addPerscriptiveAciTuples( opContext, tuples, normName, clonedEntry ); 1359 addEntryAciTuples( tuples, ( ( ClonedServerEntry ) clonedEntry ).getOriginalEntry() ); 1360 addSubentryAciTuples( opContext, tuples, normName, ( ( ClonedServerEntry ) clonedEntry ).getOriginalEntry() ); 1361 1362 AciContext aciContext = new AciContext( schemaManager, opContext ); 1363 aciContext.setUserGroupNames( userGroups ); 1364 aciContext.setUserDn( userDn ); 1365 aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); 1366 aciContext.setEntryDn( normName ); 1367 aciContext.setMicroOperations( SEARCH_ENTRY_PERMS ); 1368 aciContext.setAciTuples( tuples ); 1369 aciContext.setEntry( ( ( ClonedServerEntry ) clonedEntry ).getOriginalEntry() ); 1370 1371 if ( !engine.hasPermission( aciContext ) ) 1372 { 1373 return false; 1374 } 1375 1376 /* 1377 * For each attribute type we check if access is allowed to the type. If not 1378 * the attribute is yanked out of the entry to be returned. If permission is 1379 * allowed we move on to check if the values are allowed. Values that are 1380 * not allowed are removed from the attribute. If the attribute has no more 1381 * values remaining then the entire attribute is removed. 1382 */ 1383 List<AttributeType> attributeToRemove = new ArrayList<>(); 1384 1385 for ( Attribute attribute : clonedEntry.getAttributes() ) 1386 { 1387 // if attribute type scope access is not allowed then remove the attribute and continue 1388 AttributeType attributeType = attribute.getAttributeType(); 1389 Attribute attr = clonedEntry.get( attributeType ); 1390 1391 aciContext = new AciContext( schemaManager, opContext ); 1392 aciContext.setUserGroupNames( userGroups ); 1393 aciContext.setUserDn( userDn ); 1394 aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); 1395 aciContext.setEntryDn( normName ); 1396 aciContext.setAttributeType( attributeType ); 1397 aciContext.setMicroOperations( SEARCH_ATTRVAL_PERMS ); 1398 aciContext.setAciTuples( tuples ); 1399 aciContext.setEntry( clonedEntry ); 1400 1401 if ( !engine.hasPermission( aciContext ) ) 1402 { 1403 attributeToRemove.add( attributeType ); 1404 1405 continue; 1406 } 1407 1408 List<Value> valueToRemove = new ArrayList<>(); 1409 1410 // attribute type scope is ok now let's determine value level scope 1411 for ( Value value : attr ) 1412 { 1413 aciContext = new AciContext( schemaManager, opContext ); 1414 aciContext.setUserGroupNames( userGroups ); 1415 aciContext.setUserDn( userDn ); 1416 aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() ); 1417 aciContext.setEntryDn( normName ); 1418 aciContext.setAttributeType( attr.getAttributeType() ); 1419 aciContext.setAttrValue( value ); 1420 aciContext.setMicroOperations( SEARCH_ATTRVAL_PERMS ); 1421 aciContext.setAciTuples( tuples ); 1422 aciContext.setEntry( clonedEntry ); 1423 1424 if ( !engine.hasPermission( aciContext ) ) 1425 { 1426 valueToRemove.add( value ); 1427 } 1428 } 1429 1430 for ( Value value : valueToRemove ) 1431 { 1432 attr.remove( value ); 1433 } 1434 1435 if ( attr.size() == 0 ) 1436 { 1437 attributeToRemove.add( attributeType ); 1438 } 1439 } 1440 1441 for ( AttributeType attributeType : attributeToRemove ) 1442 { 1443 clonedEntry.removeAttributes( attributeType ); 1444 } 1445 1446 return true; 1447 } 1448 1449 /** 1450 * WARNING: create one of these filters fresh every time for each new search. 1451 */ 1452 private class AuthorizationFilter implements EntryFilter 1453 { 1454 /** 1455 * {@inheritDoc} 1456 */ 1457 @Override 1458 public boolean accept( SearchOperationContext searchContext, Entry entry ) throws LdapException 1459 { 1460 if ( !entry.getDn().isSchemaAware() ) 1461 { 1462 entry.setDn( new Dn( schemaManager, entry.getDn() ) ); 1463 } 1464 1465 return filter( searchContext, entry.getDn(), entry ); 1466 } 1467 1468 1469 /** 1470 * {@inheritDoc} 1471 */ 1472 @Override 1473 public String toString( String tabs ) 1474 { 1475 return tabs + "AuthorizationFilter"; 1476 } 1477 } 1478 1479 1480 private boolean isTheAdministrator( Dn normalizedDn ) 1481 { 1482 return normalizedDn.equals( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED ); 1483 } 1484}