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