001 /* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2008 Sun Microsystems, Inc. 026 */ 027 028 package org.opends.server.authorization.dseecompat; 029 import org.opends.messages.Message; 030 031 032 033 import static org.opends.server.authorization.dseecompat.Aci.*; 034 import static org.opends.server.config.ConfigConstants.ATTR_AUTHZ_GLOBAL_ACI; 035 import static org.opends.server.loggers.ErrorLogger.logError; 036 import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; 037 import static org.opends.server.loggers.debug.DebugLogger.getTracer; 038 import static org.opends.messages.AccessControlMessages.*; 039 import static org.opends.server.schema.SchemaConstants.SYNTAX_DN_OID; 040 import static org.opends.server.util.ServerConstants.*; 041 import static org.opends.server.util.StaticUtils.toLowerCase; 042 043 import java.util.*; 044 import java.util.concurrent.locks.Lock; 045 046 import org.opends.server.admin.std.server.DseeCompatAccessControlHandlerCfg; 047 import org.opends.server.api.AccessControlHandler; 048 import org.opends.server.config.ConfigException; 049 import org.opends.server.core.*; 050 import org.opends.server.loggers.debug.DebugTracer; 051 import org.opends.server.protocols.internal.InternalClientConnection; 052 import org.opends.server.protocols.internal.InternalSearchOperation; 053 import org.opends.server.types.*; 054 import org.opends.server.workflowelement.localbackend.*; 055 import org.opends.server.controls.GetEffectiveRights; 056 import org.opends.server.backends.jeb.EntryContainer; 057 058 059 /** 060 * The AciHandler class performs the main processing for the dseecompat package. 061 */ 062 public class AciHandler 063 extends AccessControlHandler<DseeCompatAccessControlHandlerCfg> 064 { 065 /** 066 * The tracer object for the debug logger. 067 */ 068 private static final DebugTracer TRACER = getTracer(); 069 070 071 /** 072 * The list that holds that ACIs keyed by the DN of the entry 073 * holding the ACI. 074 */ 075 private AciList aciList; 076 077 /** 078 * The listener that handles ACI changes caused by LDAP operations, ACI 079 * decode failure alert logging and backend initialization ACI list 080 * adjustment. 081 */ 082 private AciListenerManager aciListenerMgr; 083 084 /** 085 * Attribute type corresponding to "aci" attribute. 086 */ 087 static AttributeType aciType; 088 089 /** 090 * Attribute type corresponding to global "ds-cfg-global-aci" attribute. 091 */ 092 static AttributeType globalAciType; 093 094 /** 095 * Attribute type corresponding to "debugsearchindex" attribute. 096 */ 097 static AttributeType debugSearchIndex; 098 099 /** 100 * Attribute type corresponding to the "ref" attribute type. Used in the 101 * search reference access check. 102 */ 103 static AttributeType refAttrType; 104 105 /* 106 * DN corresponding to "debugsearchindex" attribute type. 107 */ 108 static DN debugSearchIndexDN; 109 110 111 /** 112 * String used to save the original authorization entry in an operation 113 * attachment if a proxied authorization control was seen. 114 */ 115 public static final String ORIG_AUTH_ENTRY="origAuthorizationEntry"; 116 117 /** 118 * String used to save a resource entry containing all the attributes in 119 * the SearchOperation attachment list. This is only used during 120 * geteffectiverights read right processing when all of an entry'ss 121 * attributes need to examined. 122 */ 123 public static final String ALL_ATTRS_RESOURCE_ENTRY = "allAttrsResourceEntry"; 124 125 /** 126 * String used to indicate that the evaluating ACI had a all user attributes 127 * targetattr match (targetattr="*"). 128 */ 129 public static final String ALL_USER_ATTRS_MATCHED = "allUserAttrsMatched"; 130 131 /** 132 * String used to indicate that the evaluating ACI had a all operational 133 * attributes targetattr match (targetattr="+"). 134 */ 135 public static final String ALL_OP_ATTRS_MATCHED = "allOpAttrsMatched"; 136 137 static { 138 initStatics(); 139 } 140 141 // We initialize these for each new AciHandler so that we can clear out 142 // the stale references that can occur during an in-core restart. 143 private static void initStatics() 144 { 145 if((aciType = DirectoryServer.getAttributeType("aci")) == null) 146 { 147 aciType = DirectoryServer.getDefaultAttributeType("aci"); 148 } 149 150 if((globalAciType = 151 DirectoryServer.getAttributeType(ATTR_AUTHZ_GLOBAL_ACI)) == null) 152 { 153 globalAciType = 154 DirectoryServer.getDefaultAttributeType(ATTR_AUTHZ_GLOBAL_ACI); 155 } 156 157 if((debugSearchIndex = 158 DirectoryServer. 159 getAttributeType(EntryContainer.ATTR_DEBUG_SEARCH_INDEX)) == null) 160 { 161 debugSearchIndex = 162 DirectoryServer. 163 getDefaultAttributeType(EntryContainer.ATTR_DEBUG_SEARCH_INDEX); 164 } 165 166 if((refAttrType = 167 DirectoryServer. 168 getAttributeType(ATTR_REFERRAL_URL)) == null) { 169 refAttrType = 170 DirectoryServer. 171 getDefaultAttributeType(ATTR_REFERRAL_URL); 172 } 173 try { 174 debugSearchIndexDN=DN.decode("cn=debugsearch"); 175 } catch (DirectoryException ex) { 176 //Should never happen. 177 } 178 } 179 180 /** 181 * Creates a new DSEE-compatible access control handler. 182 */ 183 public AciHandler() 184 { 185 // No implementation required. All initialization should be done in the 186 // intializeAccessControlHandler method. 187 } 188 189 190 191 /** 192 * {@inheritDoc} 193 */ 194 @Override() 195 public void initializeAccessControlHandler( 196 DseeCompatAccessControlHandlerCfg configuration) 197 throws ConfigException, InitializationException 198 { 199 initStatics(); 200 DN configurationDN=configuration.dn(); 201 aciList = new AciList(configurationDN); 202 aciListenerMgr = new AciListenerManager(aciList, configurationDN); 203 processGlobalAcis(configuration); 204 processConfigAcis(); 205 DirectoryServer.registerSupportedControl(OID_GET_EFFECTIVE_RIGHTS); 206 } 207 208 209 210 /** 211 * {@inheritDoc} 212 */ 213 @Override() 214 public void finalizeAccessControlHandler() 215 { 216 aciListenerMgr.finalizeListenerManager(); 217 AciEffectiveRights.finalizeOnShutdown(); 218 DirectoryServer.deregisterSupportedControl(OID_GET_EFFECTIVE_RIGHTS); 219 } 220 221 222 223 /** 224 * Process all global ACI attribute types found in the configuration 225 * entry and adds them to that ACI list cache. It also logs messages about 226 * the number of ACI attribute types added to the cache. This method is 227 * called once at startup. It also will put the server into lockdown 228 * mode if needed. 229 * 230 * @param configuration The config handler containing the ACI 231 * configuration information. 232 * @throws InitializationException If there is an error reading 233 * the global ACIs from the configuration entry. 234 */ 235 private void processGlobalAcis( 236 DseeCompatAccessControlHandlerCfg configuration) 237 throws InitializationException { 238 SortedSet<Aci> globalAcis = configuration.getGlobalACI(); 239 try { 240 if (globalAcis != null) { 241 aciList.addAci(DN.nullDN(),globalAcis); 242 Message message = INFO_ACI_ADD_LIST_GLOBAL_ACIS.get( 243 Integer.toString(globalAcis.size())); 244 logError(message); 245 } 246 } catch (Exception e) { 247 if (debugEnabled()) 248 TRACER.debugCaught(DebugLogLevel.ERROR, e); 249 Message message = INFO_ACI_HANDLER_FAIL_PROCESS_GLOBAL_ACI. 250 get(String.valueOf(configuration.dn())); 251 throw new InitializationException(message, e); 252 } 253 } 254 255 /** 256 * Process all ACIs under the "cn=config" naming context and adds them to 257 * the ACI list cache. It also logs messages about the number of ACIs added 258 * to the cache. This method is called once at startup. It will put the 259 * server in lockdown mode if needed. 260 * 261 * @throws InitializationException If there is an error searching for 262 * the ACIs in the naming context. 263 */ 264 private void processConfigAcis() throws InitializationException { 265 try 266 { 267 DN configDN=DN.decode("cn=config"); 268 LinkedHashSet<String> attrs = new LinkedHashSet<String>(1); 269 attrs.add("aci"); 270 LinkedList<Message>failedACIMsgs=new LinkedList<Message>(); 271 InternalClientConnection conn = 272 InternalClientConnection.getRootConnection(); 273 InternalSearchOperation op = conn.processSearch(configDN, 274 SearchScope.WHOLE_SUBTREE, 275 DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false, 276 SearchFilter.createFilterFromString("aci=*"), attrs); 277 if(!op.getSearchEntries().isEmpty()) { 278 int validAcis = 279 aciList.addAci(op.getSearchEntries(), failedACIMsgs); 280 if(!failedACIMsgs.isEmpty()) 281 aciListenerMgr.logMsgsSetLockDownMode(failedACIMsgs); 282 Message message = INFO_ACI_ADD_LIST_ACIS.get( 283 Integer.toString(validAcis), String.valueOf(configDN)); 284 logError(message); 285 } 286 } catch (DirectoryException e) { 287 Message message = INFO_ACI_HANDLER_FAIL_PROCESS_ACI.get(); 288 throw new InitializationException(message, e); 289 } 290 } 291 292 293 /** 294 * Checks to see if a LDAP modification is allowed access. 295 * 296 * @param container The structure containing the LDAP modifications 297 * @param operation The operation to check modify privileges on. 298 * operation to check and the evaluation context to apply the check against. 299 * @param skipAccessCheck True if access checking should be skipped. 300 * @return True if access is allowed. 301 */ 302 private boolean aciCheckMods(AciLDAPOperationContainer container, 303 LocalBackendModifyOperation operation, 304 boolean skipAccessCheck) { 305 Entry resourceEntry=container.getResourceEntry(); 306 DN dn=resourceEntry.getDN(); 307 List<Modification> modifications=container.getModifications(); 308 for(Modification m : modifications) { 309 Attribute modAttr=m.getAttribute(); 310 AttributeType modAttrType=modAttr.getAttributeType(); 311 312 if(modAttrType.equals(aciType)) { 313 /* 314 * Check that the operation has modify privileges if 315 * it contains an "aci" attribute type. 316 */ 317 if (!operation.getClientConnection(). 318 hasPrivilege(Privilege.MODIFY_ACL, operation)) { 319 Message message = INFO_ACI_MODIFY_FAILED_PRIVILEGE. 320 get(String.valueOf(container.getResourceDN()), 321 String.valueOf(container.getClientDN())); 322 logError(message); 323 return false; 324 } 325 } 326 //This access check handles the case where all attributes of this 327 //type are being replaced or deleted. If only a subset is being 328 //deleted than this access check is skipped. 329 ModificationType modType=m.getModificationType(); 330 if((modType == ModificationType.DELETE && 331 modAttr.getValues().isEmpty()) || 332 (modType == ModificationType.REPLACE || 333 modType == ModificationType.INCREMENT)) { 334 /* 335 * Check if we have rights to delete all values of 336 * an attribute type in the resource entry. 337 */ 338 if(resourceEntry.hasAttribute(modAttrType)) { 339 container.setCurrentAttributeType(modAttrType); 340 List<Attribute> attrList = 341 resourceEntry.getAttribute(modAttrType,modAttr.getOptions()); 342 if(attrList != null) { 343 for (Attribute a : attrList) { 344 for (AttributeValue v : a.getValues()) { 345 container.setCurrentAttributeValue(v); 346 container.setRights(ACI_WRITE_DELETE); 347 if(!skipAccessCheck && 348 !accessAllowed(container)) 349 return false; 350 } 351 } 352 } 353 } 354 } 355 356 if(modAttr.hasValue()) { 357 for(AttributeValue v : modAttr.getValues()) { 358 container.setCurrentAttributeType(modAttrType); 359 switch (m.getModificationType()) 360 { 361 case ADD: 362 case REPLACE: 363 container.setCurrentAttributeValue(v); 364 container.setRights(ACI_WRITE_ADD); 365 if(!skipAccessCheck && !accessAllowed(container)) 366 return false; 367 break; 368 case DELETE: 369 container.setCurrentAttributeValue(v); 370 container.setRights(ACI_WRITE_DELETE); 371 if(!skipAccessCheck && !accessAllowed(container)) 372 return false; 373 break; 374 case INCREMENT: 375 Entry modifiedEntry = operation.getModifiedEntry(); 376 List<Attribute> modifiedAttrs = 377 modifiedEntry.getAttribute(modAttrType, 378 modAttr.getOptions()); 379 if (modifiedAttrs != null) 380 { 381 for (Attribute attr : modifiedAttrs) 382 { 383 for (AttributeValue val : attr.getValues()) 384 { 385 container.setCurrentAttributeValue(val); 386 container.setRights(ACI_WRITE_ADD); 387 if(!skipAccessCheck && !accessAllowed(container)) 388 return false; 389 } 390 } 391 } 392 break; 393 } 394 /* 395 Check if the modification type has an "aci" attribute type. 396 If so, check the syntax of that attribute value. Fail the 397 the operation if the syntax check fails. 398 */ 399 if(modAttrType.equals(aciType) || 400 modAttrType.equals(globalAciType)) { 401 try { 402 //A global ACI needs a NULL DN, not the DN of the 403 //modification. 404 if(modAttrType.equals(globalAciType)) 405 dn=DN.nullDN(); 406 Aci.decode(v.getValue(),dn); 407 } catch (AciException ex) { 408 Message message = WARN_ACI_MODIFY_FAILED_DECODE.get( 409 String.valueOf(dn), ex.getMessage()); 410 logError(message); 411 return false; 412 } 413 } 414 } 415 } 416 } 417 return true; 418 } 419 420 /** 421 * Performs the test of the deny and allow access lists using the 422 * provided evaluation context. The deny list is checked first. 423 * 424 * @param evalCtx The evaluation context to use. 425 * @return True if access is allowed. 426 */ 427 private boolean testApplicableLists(AciEvalContext evalCtx) { 428 EnumEvalResult res; 429 evalCtx.setEvalReason(EnumEvalReason.NO_REASON); 430 LinkedList<Aci>denys=evalCtx.getDenyList(); 431 LinkedList<Aci>allows=evalCtx.getAllowList(); 432 //If allows list is empty and not doing geteffectiverights return 433 //false. 434 if(allows.isEmpty() && !(evalCtx.isGetEffectiveRightsEval() && 435 !evalCtx.hasRights(ACI_SELF) && 436 evalCtx.isTargAttrFilterMatchAciEmpty())) { 437 evalCtx.setEvalReason(EnumEvalReason.NO_ALLOW_ACIS); 438 evalCtx.setDecidingAci(null); 439 return false; 440 } 441 evalCtx.setDenyEval(true); 442 for(Aci denyAci : denys) { 443 res=Aci.evaluate(evalCtx, denyAci); 444 //Failure could be returned if a system limit is hit or 445 //search fails 446 if(res.equals(EnumEvalResult.FAIL)) { 447 evalCtx.setEvalReason(EnumEvalReason.EVALUATED_DENY_ACI); 448 evalCtx.setDecidingAci(denyAci); 449 return false; 450 } else if (res.equals(EnumEvalResult.TRUE)) { 451 if(evalCtx.isGetEffectiveRightsEval() && 452 !evalCtx.hasRights(ACI_SELF) && 453 !evalCtx.isTargAttrFilterMatchAciEmpty()) { 454 //Iterate to next only if deny ACI contains a targattrfilters 455 //keyword. 456 if(AciEffectiveRights.setTargAttrAci(evalCtx, denyAci, true)) 457 continue; 458 evalCtx.setEvalReason(EnumEvalReason.EVALUATED_DENY_ACI); 459 evalCtx.setDecidingAci(denyAci); 460 return false; 461 } else { 462 evalCtx.setEvalReason(EnumEvalReason.EVALUATED_DENY_ACI); 463 evalCtx.setDecidingAci(denyAci); 464 return false; 465 } 466 } 467 } 468 //Now check the allows -- flip the deny flag to false first. 469 evalCtx.setDenyEval(false); 470 for(Aci allowAci : allows) { 471 res=Aci.evaluate(evalCtx, allowAci); 472 if(res.equals(EnumEvalResult.TRUE)) { 473 if(evalCtx.isGetEffectiveRightsEval() && 474 !evalCtx.hasRights(ACI_SELF) && 475 !evalCtx.isTargAttrFilterMatchAciEmpty()) { 476 //Iterate to next only if deny ACI contains a targattrfilters 477 //keyword. 478 if(AciEffectiveRights.setTargAttrAci(evalCtx, allowAci, false)) 479 continue; 480 evalCtx.setEvalReason(EnumEvalReason.EVALUATED_ALLOW_ACI); 481 evalCtx.setDecidingAci(allowAci); 482 return true; 483 } else { 484 evalCtx.setEvalReason(EnumEvalReason.EVALUATED_ALLOW_ACI); 485 evalCtx.setDecidingAci(allowAci); 486 return true; 487 } 488 } 489 } 490 //Nothing matched fall through. 491 evalCtx.setEvalReason(EnumEvalReason.NO_MATCHED_ALLOWS_ACIS); 492 evalCtx.setDecidingAci(null); 493 return false; 494 } 495 496 /** 497 * Creates the allow and deny ACI lists based on the provided target 498 * match context. These lists are stored in the evaluation context. 499 * @param candidates List of all possible ACI candidates. 500 * @param targetMatchCtx Target matching context to use for testing each 501 * ACI. 502 */ 503 private void createApplicableList(LinkedList<Aci> candidates, 504 AciTargetMatchContext targetMatchCtx) 505 { 506 LinkedList<Aci>denys=new LinkedList<Aci>(); 507 LinkedList<Aci>allows=new LinkedList<Aci>(); 508 for(Aci aci : candidates) { 509 if(Aci.isApplicable(aci, targetMatchCtx)) { 510 if (aci.hasAccessType(EnumAccessType.DENY)) { 511 denys.add(aci); 512 } 513 if(aci.hasAccessType(EnumAccessType.ALLOW)) { 514 allows.add(aci); 515 } 516 } 517 if(targetMatchCtx.getTargAttrFiltersMatch()) 518 targetMatchCtx.setTargAttrFiltersMatch(false); 519 } 520 targetMatchCtx.setAllowList(allows); 521 targetMatchCtx.setDenyList(denys); 522 } 523 524 525 /** 526 * Check to see if the client entry has BYPASS_ACL privileges 527 * for this operation. 528 * @param operation The operation to check privileges on. 529 * @return True if access checking can be skipped because 530 * the operation client connection has BYPASS_ACL privileges. 531 */ 532 private boolean skipAccessCheck(Operation operation) { 533 return operation.getClientConnection(). 534 hasPrivilege(Privilege.BYPASS_ACL, operation); 535 } 536 537 /** 538 * Check access using the specified container. This container will have all 539 * of the information to gather applicable ACIs and perform evaluation on 540 * them. 541 * 542 * @param container An ACI operation container which has all of the 543 * information needed to check access. 544 * 545 * @return True if access is allowed. 546 */ 547 boolean accessAllowed(AciContainer container) 548 { 549 DN dn = container.getResourceEntry().getDN(); 550 //For ACI_WRITE_ADD and ACI_WRITE_DELETE set the ACI_WRITE 551 //right. 552 if(container.hasRights(ACI_WRITE_ADD) || 553 container.hasRights(ACI_WRITE_DELETE)) 554 container.setRights(container.getRights() | ACI_WRITE); 555 //Check if the ACI_SELF right needs to be set (selfwrite right). 556 //Only done if the right is ACI_WRITE, an attribute value is set and 557 //that attribute value is a DN. 558 if((container.getCurrentAttributeValue() != null) && 559 (container.hasRights(ACI_WRITE)) && 560 (isAttributeDN(container.getCurrentAttributeType()))) { 561 String DNString=null; 562 try { 563 DNString = container.getCurrentAttributeValue().getStringValue(); 564 DN tmpDN = DN.decode(DNString); 565 //Have a valid DN, compare to clientDN to see if the ACI_SELF 566 //right should be set. 567 if(tmpDN.equals(container.getClientDN())) { 568 container.setRights(container.getRights() | ACI_SELF); 569 } 570 } catch (DirectoryException ex) { 571 //Log a message and keep going. 572 Message message = WARN_ACI_NOT_VALID_DN.get(DNString); 573 logError(message); 574 } 575 } 576 577 //Check proxy authorization only if the entry has not already been 578 //processed (working on a new entry). If working on a new entry, then 579 //only do a proxy check if the right is not set to ACI_PROXY and the 580 //proxied authorization control has been decoded. 581 if(!container.hasSeenEntry()) { 582 if(container.isProxiedAuthorization() && 583 !container.hasRights(ACI_PROXY) && 584 !container.hasRights(ACI_SKIP_PROXY_CHECK)) { 585 int currentRights=container.getRights(); 586 //Save the current rights so they can be put back if on success. 587 container.setRights(ACI_PROXY); 588 //Switch to the original authorization entry, not the proxied one. 589 container.useOrigAuthorizationEntry(true); 590 if(!accessAllowed(container)) 591 return false; 592 //Access is ok, put the original rights back. 593 container.setRights(currentRights); 594 //Put the proxied authorization entry back to the current 595 //authorization entry. 596 container.useOrigAuthorizationEntry(false); 597 } 598 //Set the seen flag so proxy processing is not performed for this 599 //entry again. 600 container.setSeenEntry(true); 601 } 602 603 /* 604 * First get all allowed candidate ACIs. 605 */ 606 LinkedList<Aci>candidates = aciList.getCandidateAcis(dn); 607 /* 608 * Create an applicable list of ACIs by target matching each 609 * candidate ACI against the container's target match view. 610 */ 611 createApplicableList(candidates,container); 612 /* 613 * Evaluate the applicable list. 614 */ 615 boolean ret=testApplicableLists(container); 616 //Build summary string if doing geteffectiverights eval. 617 if(container.isGetEffectiveRightsEval()) 618 AciEffectiveRights.createSummary(container, ret, "main"); 619 return ret; 620 } 621 622 /** 623 * Check if the specified attribute type is a DN by checking if its syntax 624 * OID is equal to the DN syntax OID. 625 * @param attribute The attribute type to check. 626 * @return True if the attribute type syntax OID is equal to a DN syntax 627 * OID. 628 */ 629 private boolean isAttributeDN(AttributeType attribute) { 630 return (attribute.getSyntaxOID().equals(SYNTAX_DN_OID)); 631 } 632 633 /** 634 * Performs an access check against all of the attributes of an entry. 635 * The attributes that fail access are removed from the entry. This method 636 * performs the processing needed for the filterEntry method processing. 637 * 638 * @param container The search or compare container which has all of the 639 * information needed to filter the attributes for this entry. 640 * @return The entry to send back to the client, minus any attribute 641 * types that failed access check. 642 */ 643 private SearchResultEntry 644 accessAllowedAttrs(AciLDAPOperationContainer container) { 645 Entry e=container.getResourceEntry(); 646 List<AttributeType> typeList=getAllAttrs(e); 647 for(AttributeType attrType : typeList) { 648 if(container.hasAllUserAttributes() && !attrType.isOperational()) 649 continue; 650 if(container.hasAllOpAttributes() && attrType.isOperational()) 651 continue; 652 container.setCurrentAttributeType(attrType); 653 if(!accessAllowed(container)) 654 e.removeAttribute(attrType); 655 } 656 return container.getSearchResultEntry(); 657 } 658 659 /** 660 * Gathers all of the attribute types in an entry along with the 661 * "objectclass" attribute type in a List. The "objectclass" attribute is 662 * added to the list first so it is evaluated first. 663 * 664 * @param e Entry to gather the attributes for. 665 * @return List containing the attribute types. 666 */ 667 private List<AttributeType> getAllAttrs(Entry e) { 668 Map<AttributeType,List<Attribute>> attrMap = e.getUserAttributes(); 669 Map<AttributeType,List<Attribute>> opAttrMap = 670 e.getOperationalAttributes(); 671 List<AttributeType> typeList=new LinkedList<AttributeType>(); 672 Attribute attr=e.getObjectClassAttribute(); 673 /* 674 * When a search is not all attributes returned, the "objectclass" 675 * attribute type is missing from the entry. 676 */ 677 if(attr != null) { 678 AttributeType ocType=attr.getAttributeType(); 679 typeList.add(ocType); 680 } 681 typeList.addAll(attrMap.keySet()); 682 typeList.addAll(opAttrMap.keySet()); 683 return typeList; 684 } 685 686 /* 687 * TODO Evaluate performance of this method. 688 * TODO Evaluate security concerns of this method. Logic from this method 689 * taken almost directly from DS6 implementation. 690 * 691 * I find the work done in the accessAllowedEntry method, particularly 692 * with regard to the entry test evaluation, to be very confusing and 693 * potentially pretty inefficient. I'm also concerned that the "return 694 * "true" inside the for loop could potentially allow access when it 695 * should be denied. 696 */ 697 /** 698 * Check if access is allowed on an entry. Access is checked by iterating 699 * through each attribute of an entry, starting with the "objectclass" 700 * attribute type. 701 * 702 * If access is allowed on the entry based on one of it's attribute types, 703 * then a possible second access check is performed. This second check is 704 * only performed if an entry test ACI was found during the earlier 705 * successful access check. An entry test ACI has no "targetattrs" keyword, 706 * so allowing access based on an attribute type only would be incorrect. 707 * 708 * @param container ACI search container containing all of the information 709 * needed to check access. 710 * 711 * @return True if access is allowed. 712 */ 713 boolean accessAllowedEntry(AciLDAPOperationContainer container) { 714 boolean ret=false; 715 //set flag that specifies this is the first attribute evaluated 716 //in the entry 717 container.setIsFirstAttribute(true); 718 List<AttributeType> typeList=getAllAttrs(container.getResourceEntry()); 719 for(AttributeType attrType : typeList) { 720 container.setCurrentAttributeType(attrType); 721 /* 722 * Check if access is allowed. If true, then check to see if an 723 * entry test rule was found (no targetattrs) during target match 724 * evaluation. If such a rule was found, set the current attribute 725 * type to "null" and check access again so that rule is applied. 726 */ 727 if(accessAllowed(container)) { 728 if(container.hasEntryTestRule()) { 729 container.setCurrentAttributeType(null); 730 if(!accessAllowed(container)) { 731 /* 732 * If we failed because of a deny permission-bind rule, 733 * we need to stop and return false. 734 */ 735 if(container.isDenyEval()) { 736 return false; 737 } 738 /* 739 * If we failed because there was no explicit 740 * allow rule, then we grant implicit access to the 741 * entry. 742 */ 743 } 744 } 745 return true; 746 } 747 } 748 return ret; 749 } 750 751 /** 752 * Test the attribute types of the search filter for access. This method 753 * supports the search right. 754 * 755 * @param container The container used in the access evaluation. 756 * @param filter The filter to check access on. 757 * @return True if all attribute types in the filter have access. 758 * @throws DirectoryException If there is a problem matching the entry 759 * using the provided filter. 760 */ 761 private boolean 762 testFilter(AciLDAPOperationContainer container, SearchFilter filter) 763 throws DirectoryException { 764 boolean ret=true; 765 //If the resource entry has a dn equal to "cn=debugsearch" and it 766 //contains the special attribute type "debugsearchindex", then the 767 //resource entry is a psudo entry created for debug purposes. Return 768 //true if that is the case. 769 if(debugSearchIndexDN.equals(container.getResourceDN()) && 770 container.getResourceEntry().hasAttribute(debugSearchIndex)) 771 return true; 772 switch (filter.getFilterType()) { 773 case AND: 774 case OR: { 775 for (SearchFilter f : filter.getFilterComponents()) 776 if(!testFilter(container, f)) 777 return false ; 778 break; 779 } 780 case NOT: { 781 ret=false; 782 SearchFilter f = filter.getNotComponent(); 783 if(f.matchesEntry(container.getResourceEntry())) 784 ret=true; 785 if(ret) 786 ret=testFilter(container, f); 787 ret=!ret; 788 break; 789 } 790 default: { 791 AttributeType attrType=filter.getAttributeType(); 792 container.setCurrentAttributeType(attrType); 793 ret=accessAllowed(container); 794 } 795 } 796 return ret; 797 } 798 799 /** 800 * Check access using the accessAllowed method. The 801 * LDAP add, compare, modify and delete operations use this function. 802 * The other supported LDAP operations have more specialized checks. 803 * @param operationContainer The container containing the information 804 * needed to evaluate this operation. 805 * @param operation The operation being evaluated. 806 * @return True if this operation is allowed access. 807 */ 808 private boolean isAllowed(AciLDAPOperationContainer operationContainer, 809 Operation operation) { 810 return skipAccessCheck(operation) || accessAllowed(operationContainer); 811 } 812 813 /** 814 * Evaluate an entry to be added to see if it has any "aci" 815 * attribute type. If it does, examines each "aci" attribute type 816 * value for syntax errors. All of the "aci" attribute type values 817 * must pass syntax check for the add operation to proceed. Any 818 * entry with an "aci" attribute type must have "modify-acl" 819 * privileges. 820 * 821 * @param entry The entry to be examined. 822 * @param operation The operation to to check privileges on. 823 * @param clientDN The authorization DN. 824 * @return True if the entry has no ACI attributes or if all of the "aci" 825 * attributes values pass ACI syntax checking. 826 */ 827 private boolean 828 verifySyntax(Entry entry, Operation operation, DN clientDN) { 829 if(entry.hasOperationalAttribute(aciType)) { 830 /* 831 * Check that the operation has "modify-acl" privileges since the 832 * entry to be added has an "aci" attribute type. 833 */ 834 if (!operation.getClientConnection(). 835 hasPrivilege(Privilege.MODIFY_ACL, operation)) { 836 Message message = INFO_ACI_ADD_FAILED_PRIVILEGE.get( 837 String.valueOf(entry.getDN()), String.valueOf(clientDN)); 838 logError(message); 839 return false; 840 } 841 List<Attribute> attributeList = 842 entry.getOperationalAttribute(aciType, null); 843 for (Attribute attribute : attributeList) 844 { 845 for (AttributeValue value : attribute.getValues()) 846 { 847 try { 848 DN dn=entry.getDN(); 849 Aci.decode(value.getValue(),dn); 850 } catch (AciException ex) { 851 Message message = WARN_ACI_ADD_FAILED_DECODE.get( 852 String.valueOf(entry.getDN()), ex.getMessage()); 853 logError(message); 854 return false; 855 } 856 } 857 } 858 } 859 return true; 860 } 861 862 /** 863 * Check access on add operations. 864 * 865 * @param operation The add operation to check access on. 866 * @return True if access is allowed. 867 */ 868 public boolean isAllowed(LocalBackendAddOperation operation) { 869 AciLDAPOperationContainer operationContainer = 870 new AciLDAPOperationContainer(operation, ACI_ADD); 871 boolean ret=isAllowed(operationContainer,operation); 872 873 //LDAP add needs a verify ACI syntax step in case any 874 //"aci" attribute types are being added. 875 if(ret) 876 ret=verifySyntax(operation.getEntryToAdd(), operation, 877 operationContainer.getClientDN()); 878 return ret; 879 } 880 881 /** 882 * Check access on compare operations. Note that the attribute 883 * type is unavailable at this time, so this method partially 884 * parses the raw attribute string to get the base attribute 885 * type. Options are ignored. 886 * 887 * @param operation The compare operation to check access on. 888 * @return True if access is allowed. 889 */ 890 public boolean isAllowed(LocalBackendCompareOperation operation) { 891 AciLDAPOperationContainer operationContainer = 892 new AciLDAPOperationContainer(operation, ACI_COMPARE); 893 String baseName; 894 String rawAttributeType=operation.getRawAttributeType(); 895 int semicolonPosition=rawAttributeType.indexOf(';'); 896 if (semicolonPosition > 0) 897 baseName = 898 toLowerCase(rawAttributeType.substring(0, semicolonPosition)); 899 else 900 baseName = toLowerCase(rawAttributeType); 901 AttributeType attributeType; 902 if((attributeType = 903 DirectoryServer.getAttributeType(baseName)) == null) 904 attributeType = DirectoryServer.getDefaultAttributeType(baseName); 905 AttributeValue attributeValue = 906 new AttributeValue(attributeType, operation.getAssertionValue()); 907 operationContainer.setCurrentAttributeType(attributeType); 908 operationContainer.setCurrentAttributeValue(attributeValue); 909 return isAllowed(operationContainer, operation); 910 } 911 912 /** 913 * Check access on delete operations. 914 * 915 * @param operation The delete operation to check access on. 916 * @return True if access is allowed. 917 */ 918 public boolean isAllowed(LocalBackendDeleteOperation operation) { 919 AciLDAPOperationContainer operationContainer= 920 new AciLDAPOperationContainer(operation, ACI_DELETE); 921 return isAllowed(operationContainer, operation); 922 } 923 924 /** 925 * Check access on modify operations. 926 * 927 * @param operation The modify operation to check access on. 928 * @return True if access is allowed. 929 */ 930 931 public boolean isAllowed(LocalBackendModifyOperation operation) { 932 AciLDAPOperationContainer operationContainer= 933 new AciLDAPOperationContainer(operation, ACI_NULL); 934 return aciCheckMods(operationContainer, operation, 935 skipAccessCheck(operation)); 936 } 937 938 /** 939 * Checks access on a search operation. 940 * @param operation The search operation class containing information to 941 * check the access on. 942 * @param entry The entry to evaluate access. 943 * @return True if access is allowed. 944 */ 945 public boolean 946 maySend(SearchOperation operation, SearchResultEntry entry) { 947 AciLDAPOperationContainer operationContainer = 948 new AciLDAPOperationContainer(operation, 949 (ACI_SEARCH), entry); 950 boolean ret; 951 if(!(ret=skipAccessCheck(operation))) { 952 try { 953 ret=testFilter(operationContainer, operation.getFilter()); 954 } catch (DirectoryException ex) { 955 ret=false; 956 } 957 if (ret) { 958 operationContainer.clearEvalAttributes(ACI_NULL); 959 operationContainer.setRights(ACI_READ); 960 ret=accessAllowedEntry(operationContainer); 961 if(ret) { 962 if(!operationContainer.hasEvalUserAttributes()) 963 operation.setAttachment(ALL_USER_ATTRS_MATCHED, 964 ALL_USER_ATTRS_MATCHED); 965 if(!operationContainer.hasEvalOpAttributes()) 966 operation.setAttachment(ALL_OP_ATTRS_MATCHED, 967 ALL_OP_ATTRS_MATCHED); 968 } 969 } 970 } 971 //Save a copy of the full resource entry for possible 972 //userattr bind rule or geteffectiveright's evaluations in the filterEnty 973 //method. 974 operation.setAttachment(ALL_ATTRS_RESOURCE_ENTRY, entry ); 975 return ret; 976 } 977 978 /* 979 * TODO Rename this method. Needs to be changed in SearchOperation. 980 * 981 * I find the name of the filterEntry method to be misleading because 982 * it works on a search operation but has nothing to do with the search 983 * filter. Something like "removeDisallowedAttributes" would be clearer. 984 */ 985 /** 986 * Checks access on each attribute in an entry. It removes those attributes 987 * that fail access check. 988 * 989 * @param operation The search operation class containing information to 990 * check access on. 991 * @param entry The entry containing the attributes. 992 * @return The entry to return minus filtered attributes. 993 */ 994 public SearchResultEntry filterEntry(SearchOperation operation, 995 SearchResultEntry entry) { 996 AciLDAPOperationContainer operationContainer = 997 new AciLDAPOperationContainer(operation, 998 (ACI_READ), entry); 999 //Proxy access check has already been done for this entry in the maySend 1000 //method, set the seen flag to true to bypass any proxy check. 1001 operationContainer.setSeenEntry(true); 1002 SearchResultEntry returnEntry; 1003 boolean skipCheck=skipAccessCheck(operation); 1004 if(!skipCheck) { 1005 returnEntry=accessAllowedAttrs(operationContainer); 1006 } else 1007 returnEntry=entry; 1008 if(operationContainer.hasGetEffectiveRightsControl()) { 1009 returnEntry = 1010 AciEffectiveRights.addRightsToEntry(this, operation.getAttributes(), 1011 operationContainer, returnEntry, 1012 skipCheck); 1013 } 1014 return returnEntry; 1015 } 1016 1017 /** 1018 * Perform all needed RDN checks for the modifyDN operation. The old RDN is 1019 * not equal to the new RDN. The access checks are: 1020 * 1021 * - Verify WRITE access to the original entry. 1022 * - Verfiy WRITE_ADD access on each RDN component of the new RDN. The 1023 * WRITE_ADD access is used because this access could be restricted by 1024 * the targattrfilters keyword. 1025 * - If the deleteOLDRDN flag is set, verify WRITE_DELETE access on the 1026 * old RDN. The WRITE_DELETE access is used because this access could be 1027 * restricted by the targattrfilters keyword. 1028 * 1029 * @param operation The ModifyDN operation class containing information to 1030 * check access on. 1031 * @param oldRDN The old RDN component. 1032 * @param newRDN The new RDN component. 1033 * @return True if access is allowed. 1034 */ 1035 private boolean aciCheckRDNs(LocalBackendModifyDNOperation operation, 1036 RDN oldRDN, 1037 RDN newRDN) { 1038 boolean ret; 1039 1040 AciLDAPOperationContainer operationContainer = 1041 new AciLDAPOperationContainer(operation, (ACI_WRITE), 1042 operation.getOriginalEntry()); 1043 ret=accessAllowed(operationContainer); 1044 if(ret) 1045 ret=checkRDN(ACI_WRITE_ADD, newRDN, operationContainer); 1046 if(ret && operation.deleteOldRDN()) { 1047 ret = 1048 checkRDN(ACI_WRITE_DELETE, oldRDN, operationContainer); 1049 } 1050 return ret; 1051 } 1052 1053 1054 /** 1055 * Check access on each attribute-value pair component of the specified RDN. 1056 * There may be more than one attribute-value pair if the RDN is multi-valued. 1057 * 1058 * @param right The access right to check for. 1059 * @param rdn The RDN to examine the attribute-value pairs of. 1060 * @param container The container containing the information needed to 1061 * evaluate the specified RDN. 1062 * @return True if access is allowed for all attribute-value pairs. 1063 */ 1064 private boolean checkRDN(int right, RDN rdn, AciContainer container) { 1065 boolean ret=false; 1066 int numAVAs = rdn.getNumValues(); 1067 container.setRights(right); 1068 for (int i = 0; i < numAVAs; i++){ 1069 AttributeType type=rdn.getAttributeType(i); 1070 AttributeValue value=rdn.getAttributeValue(i); 1071 container.setCurrentAttributeType(type); 1072 container.setCurrentAttributeValue(value); 1073 if(!(ret=accessAllowed(container))) 1074 break; 1075 } 1076 return ret; 1077 } 1078 1079 /** 1080 * Check access on the new superior entry if it exists. If the entry does not 1081 * exist or the DN cannot be locked then false is returned. 1082 * 1083 * @param superiorDN The DN of the new superior entry. 1084 * @param op The modifyDN operation to check access on. 1085 * @return True if access is granted to the new superior entry. 1086 * @throws DirectoryException If a problem occurs while trying to 1087 * retrieve the new superior entry. 1088 */ 1089 private boolean aciCheckSuperiorEntry(DN superiorDN, 1090 LocalBackendModifyDNOperation op) 1091 throws DirectoryException { 1092 boolean ret=false; 1093 Lock entryLock = null; 1094 for (int i=0; i < 3; i++) { 1095 entryLock = LockManager.lockRead(superiorDN); 1096 if (entryLock != null) 1097 break; 1098 } 1099 if (entryLock == null) { 1100 Message message = WARN_ACI_HANDLER_CANNOT_LOCK_NEW_SUPERIOR_USER.get( 1101 String.valueOf(superiorDN)); 1102 logError(message); 1103 return false; 1104 } 1105 try { 1106 Entry superiorEntry=DirectoryServer.getEntry(superiorDN); 1107 if(superiorEntry!= null) { 1108 AciLDAPOperationContainer operationContainer = 1109 new AciLDAPOperationContainer(op, (ACI_IMPORT), 1110 superiorEntry); 1111 ret=accessAllowed(operationContainer); 1112 } 1113 } finally { 1114 LockManager.unlock(superiorDN, entryLock); 1115 } 1116 return ret; 1117 } 1118 1119 /** 1120 * Checks access on a modifyDN operation. 1121 * 1122 * @param operation The modifyDN operation to check access on. 1123 * @return True if access is allowed. 1124 * 1125 */ 1126 public boolean isAllowed(LocalBackendModifyDNOperation operation) { 1127 boolean ret=true; 1128 DN newSuperiorDN; 1129 RDN oldRDN=operation.getOriginalEntry().getDN().getRDN(); 1130 RDN newRDN=operation.getNewRDN(); 1131 if(!skipAccessCheck(operation)) { 1132 //If this is a modifyDN move to a new superior, then check if the 1133 //superior DN has import accesss. 1134 if((newSuperiorDN=operation.getNewSuperior()) != null) { 1135 try { 1136 ret=aciCheckSuperiorEntry(newSuperiorDN, operation); 1137 } catch (DirectoryException ex) { 1138 ret=false; 1139 } 1140 } 1141 boolean rdnEquals=oldRDN.equals(newRDN); 1142 //Perform the RDN access checks only if the RDNs are not equal. 1143 if(ret && !rdnEquals) 1144 ret=aciCheckRDNs(operation, oldRDN, newRDN); 1145 1146 //If this is a modifyDN move to a new superior, then check if the 1147 //original entry DN has export access. 1148 if(ret && (newSuperiorDN != null)) { 1149 AciLDAPOperationContainer operationContainer = 1150 new AciLDAPOperationContainer(operation, (ACI_EXPORT), 1151 operation.getOriginalEntry()); 1152 //The RDNs are not equal, skip the proxy check since it was 1153 //already performed in the aciCheckRDNs call above. 1154 if(!rdnEquals) 1155 operationContainer.setSeenEntry(true); 1156 ret=accessAllowed(operationContainer); 1157 } 1158 } 1159 return ret; 1160 } 1161 1162 1163 /** 1164 * {@inheritDoc} 1165 */ 1166 @Override 1167 public boolean isAllowed(DN entryDN, Operation op, Control control) { 1168 boolean ret; 1169 if(!(ret=skipAccessCheck(op))) { 1170 Entry e = new Entry(entryDN, null, null, null); 1171 AciLDAPOperationContainer operationContainer = 1172 new AciLDAPOperationContainer(op, e, control, 1173 (ACI_READ | ACI_CONTROL)); 1174 ret=accessAllowed(operationContainer); 1175 } 1176 if(control.getOID().equals(OID_PROXIED_AUTH_V2) || 1177 control.getOID().equals(OID_PROXIED_AUTH_V1)) 1178 op.setAttachment(ORIG_AUTH_ENTRY, op.getAuthorizationEntry()); 1179 else if(control.getOID().equals(OID_GET_EFFECTIVE_RIGHTS)) { 1180 try { 1181 GetEffectiveRights getEffectiveRightsControl = 1182 GetEffectiveRights.decodeControl(control); 1183 op.setAttachment(OID_GET_EFFECTIVE_RIGHTS, getEffectiveRightsControl); 1184 } catch (LDAPException le) { 1185 Message message = 1186 WARN_ACI_SYNTAX_DECODE_EFFECTIVERIGHTS_FAIL.get(le.getMessage()); 1187 logError(message); 1188 ret=false; 1189 } 1190 } 1191 return ret; 1192 } 1193 1194 1195 /** 1196 * {@inheritDoc} 1197 */ 1198 @Override 1199 public boolean isAllowed(ExtendedOperation operation) { 1200 boolean ret; 1201 if(!(ret=skipAccessCheck(operation))) { 1202 Entry e = new Entry(operation.getAuthorizationDN(), null, null, null); 1203 AciLDAPOperationContainer operationContainer = 1204 new AciLDAPOperationContainer(operation, e, (ACI_READ | ACI_EXT_OP)); 1205 ret=accessAllowed(operationContainer); 1206 } 1207 return ret; 1208 } 1209 1210 1211 /** 1212 * {@inheritDoc} 1213 */ 1214 @Override 1215 public boolean maySend(DN dn, SearchOperation operation, 1216 SearchResultReference reference) { 1217 boolean ret; 1218 if(!(ret=skipAccessCheck(operation))) { 1219 Entry e = new Entry(dn, null, null, null); 1220 LinkedHashSet<AttributeValue> vals = new LinkedHashSet<AttributeValue>(); 1221 List<String> URLStrings=reference.getReferralURLs(); 1222 //Load the values, a bind rule might want to evaluate them. 1223 for(String URLString : URLStrings) { 1224 vals.add(new AttributeValue(refAttrType, URLString)); 1225 } 1226 Attribute attr = 1227 new Attribute(refAttrType, ATTR_REFERRAL_URL, vals); 1228 e.addAttribute(attr,null); 1229 SearchResultEntry se=new SearchResultEntry(e); 1230 AciLDAPOperationContainer operationContainer = 1231 new AciLDAPOperationContainer(operation, 1232 (ACI_READ), se); 1233 operationContainer.setCurrentAttributeType(refAttrType); 1234 ret=accessAllowed(operationContainer); 1235 } 1236 return ret; 1237 } 1238 1239 1240 /** 1241 * {@inheritDoc} 1242 */ 1243 @Override 1244 public boolean isAllowed(LocalBackendBindOperation bindOperation) { 1245 //Not planned to be implemented. 1246 return true; 1247 } 1248 1249 /** 1250 * {@inheritDoc} 1251 */ 1252 @Override 1253 public boolean isAllowed(LocalBackendSearchOperation searchOperation) { 1254 //Not planned to be implemented. 1255 return true; 1256 } 1257 } 1258