001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2008-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 * Portions Copyright 2013 Manuel Gaupp 017 */ 018package org.opends.server.authorization.dseecompat; 019 020import java.util.LinkedList; 021import java.util.List; 022import java.util.SortedSet; 023 024import org.forgerock.i18n.LocalizableMessage; 025import org.forgerock.i18n.LocalizedIllegalArgumentException; 026import org.forgerock.i18n.slf4j.LocalizedLogger; 027import org.forgerock.opendj.config.server.ConfigException; 028import org.forgerock.opendj.ldap.AVA; 029import org.forgerock.opendj.ldap.AttributeDescription; 030import org.forgerock.opendj.ldap.ByteString; 031import org.forgerock.opendj.ldap.DN; 032import org.forgerock.opendj.ldap.ModificationType; 033import org.forgerock.opendj.ldap.RDN; 034import org.forgerock.opendj.ldap.ResultCode; 035import org.forgerock.opendj.ldap.SearchScope; 036import org.forgerock.opendj.ldap.schema.AttributeType; 037import org.opends.server.admin.std.server.DseeCompatAccessControlHandlerCfg; 038import org.opends.server.api.AccessControlHandler; 039import org.opends.server.api.ClientConnection; 040import org.opends.server.api.ConfigHandler; 041import org.opends.server.backends.pluggable.SuffixContainer; 042import org.opends.server.controls.GetEffectiveRightsRequestControl; 043import org.opends.server.core.BindOperation; 044import org.opends.server.core.DirectoryServer; 045import org.opends.server.core.ExtendedOperation; 046import org.opends.server.core.ModifyDNOperation; 047import org.opends.server.core.SearchOperation; 048import org.opends.server.protocols.internal.InternalClientConnection; 049import org.opends.server.protocols.internal.InternalSearchOperation; 050import org.opends.server.protocols.internal.SearchRequest; 051import org.opends.server.protocols.ldap.LDAPControl; 052import org.opends.server.types.Attribute; 053import org.opends.server.types.AttributeBuilder; 054import org.opends.server.types.AuthenticationInfo; 055import org.opends.server.types.Control; 056import org.opends.server.types.DirectoryException; 057import org.opends.server.types.Entry; 058import org.opends.server.types.InitializationException; 059import org.opends.server.types.Modification; 060import org.opends.server.types.Operation; 061import org.opends.server.types.Privilege; 062import org.opends.server.types.SearchFilter; 063import org.opends.server.types.SearchResultEntry; 064import org.opends.server.types.SearchResultReference; 065import org.opends.server.workflowelement.localbackend.LocalBackendAddOperation; 066import org.opends.server.workflowelement.localbackend.LocalBackendCompareOperation; 067import org.opends.server.workflowelement.localbackend.LocalBackendDeleteOperation; 068import org.opends.server.workflowelement.localbackend.LocalBackendModifyOperation; 069import org.opends.server.workflowelement.localbackend.LocalBackendSearchOperation; 070 071import static org.opends.messages.AccessControlMessages.*; 072import static org.opends.server.authorization.dseecompat.Aci.*; 073import static org.opends.server.authorization.dseecompat.EnumEvalReason.*; 074import static org.opends.server.config.ConfigConstants.*; 075import static org.opends.server.core.DirectoryServer.*; 076import static org.opends.server.protocols.internal.InternalClientConnection.*; 077import static org.opends.server.protocols.internal.Requests.*; 078import static org.opends.server.schema.SchemaConstants.*; 079import static org.opends.server.util.ServerConstants.*; 080import static org.opends.server.util.StaticUtils.*; 081 082/** 083 * The AciHandler class performs the main processing for the dseecompat package. 084 */ 085public final class AciHandler extends 086 AccessControlHandler<DseeCompatAccessControlHandlerCfg> 087{ 088 /** 089 * String used to indicate that the evaluating ACI had a all 090 * operational attributes targetattr match (targetattr="+"). 091 */ 092 public static final String ALL_OP_ATTRS_MATCHED = "allOpAttrsMatched"; 093 094 /** 095 * String used to indicate that the evaluating ACI had a all user 096 * attributes targetattr match (targetattr="*"). 097 */ 098 public static final String ALL_USER_ATTRS_MATCHED = "allUserAttrsMatched"; 099 100 /** 101 * String used to save the original authorization entry in an 102 * operation attachment if a proxied authorization control was seen. 103 */ 104 public static final String ORIG_AUTH_ENTRY = "origAuthorizationEntry"; 105 106 /** Attribute type corresponding to "aci" attribute. */ 107 static AttributeType aciType; 108 109 /** Attribute type corresponding to global "ds-cfg-global-aci" attribute. */ 110 static AttributeType globalAciType; 111 112 /** Attribute type corresponding to "debugsearchindex" attribute. */ 113 private static AttributeType debugSearchIndex; 114 115 /** DN corresponding to "debugsearchindex" attribute type. */ 116 private static DN debugSearchIndexDN; 117 118 /** 119 * Attribute type corresponding to the "ref" attribute type. Used in 120 * the search reference access check. 121 */ 122 private static AttributeType refAttrType; 123 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 124 125 static 126 { 127 initStatics(); 128 } 129 130 131 132 /** 133 * We initialize these for each new AciHandler so that we can clear out the 134 * stale references that can occur during an in-core restart. 135 */ 136 private static void initStatics() 137 { 138 aciType = getAttributeType("aci"); 139 globalAciType = getAttributeType(ATTR_AUTHZ_GLOBAL_ACI); 140 debugSearchIndex = getAttributeType(SuffixContainer.ATTR_DEBUG_SEARCH_INDEX); 141 refAttrType = getAttributeType(ATTR_REFERRAL_URL); 142 143 try 144 { 145 debugSearchIndexDN = DN.valueOf("cn=debugsearch"); 146 } 147 catch (LocalizedIllegalArgumentException unexpected) 148 { 149 // Should never happen. 150 } 151 } 152 153 /** The list that holds that ACIs keyed by the DN of the entry holding the ACI. */ 154 private AciList aciList; 155 156 /** 157 * The listener that handles ACI changes caused by LDAP operations, 158 * ACI decode failure alert logging and backend initialization ACI list adjustment. 159 */ 160 private AciListenerManager aciListenerMgr; 161 162 /** Creates a new DSEE-compatible access control handler. */ 163 public AciHandler() 164 { 165 // No implementation required. All initialization should be done in 166 // the intializeAccessControlHandler method. 167 } 168 169 /** {@inheritDoc} */ 170 @Override 171 public void filterEntry(Operation operation, 172 SearchResultEntry unfilteredEntry, SearchResultEntry filteredEntry) 173 { 174 AciLDAPOperationContainer container = 175 new AciLDAPOperationContainer(operation, ACI_READ, unfilteredEntry); 176 177 // Proxy access check has already been done for this entry in the 178 // maySend method, set the seen flag to true to bypass any proxy check. 179 container.setSeenEntry(true); 180 181 boolean skipCheck = skipAccessCheck(operation); 182 if (!skipCheck) 183 { 184 filterEntry(container, filteredEntry); 185 } 186 187 if (container.hasGetEffectiveRightsControl()) 188 { 189 AciEffectiveRights.addRightsToEntry(this, 190 ((SearchOperation) operation).getAttributes(), container, 191 filteredEntry, skipCheck); 192 } 193 } 194 195 /** {@inheritDoc} */ 196 @Override 197 public void finalizeAccessControlHandler() 198 { 199 aciListenerMgr.finalizeListenerManager(); 200 AciEffectiveRights.finalizeOnShutdown(); 201 DirectoryServer.deregisterSupportedControl(OID_GET_EFFECTIVE_RIGHTS); 202 } 203 204 /** {@inheritDoc} */ 205 @Override 206 public void initializeAccessControlHandler( 207 DseeCompatAccessControlHandlerCfg configuration) 208 throws ConfigException, InitializationException 209 { 210 initStatics(); 211 DN configurationDN = configuration.dn(); 212 aciList = new AciList(configurationDN); 213 aciListenerMgr = new AciListenerManager(aciList, configurationDN); 214 processGlobalAcis(configuration); 215 processConfigAcis(); 216 DirectoryServer.registerSupportedControl(OID_GET_EFFECTIVE_RIGHTS); 217 } 218 219 /** {@inheritDoc} */ 220 @Override 221 public boolean isAllowed(DN entryDN, Operation op, Control control) 222 throws DirectoryException 223 { 224 if (!skipAccessCheck(op)) 225 { 226 Entry e = new Entry(entryDN, null, null, null); 227 AciContainer container = new AciLDAPOperationContainer(op, e, control, 228 ACI_READ | ACI_CONTROL); 229 if (!accessAllowed(container)) 230 { 231 return false; 232 } 233 } 234 235 if (OID_PROXIED_AUTH_V2.equals(control.getOID()) 236 || OID_PROXIED_AUTH_V1.equals(control.getOID())) 237 { 238 op.setAttachment(ORIG_AUTH_ENTRY, op.getAuthorizationEntry()); 239 } 240 else if (OID_GET_EFFECTIVE_RIGHTS.equals(control.getOID())) 241 { 242 GetEffectiveRightsRequestControl getEffectiveRightsControl; 243 if (control instanceof LDAPControl) 244 { 245 getEffectiveRightsControl = 246 GetEffectiveRightsRequestControl.DECODER.decode(control 247 .isCritical(), ((LDAPControl) control).getValue()); 248 } 249 else 250 { 251 getEffectiveRightsControl = (GetEffectiveRightsRequestControl) control; 252 } 253 op.setAttachment(OID_GET_EFFECTIVE_RIGHTS, getEffectiveRightsControl); 254 } 255 return true; 256 } 257 258 /** {@inheritDoc} */ 259 @Override 260 public boolean isAllowed(ExtendedOperation operation) 261 { 262 if (skipAccessCheck(operation)) 263 { 264 return true; 265 } 266 267 Entry e = new Entry(operation.getAuthorizationDN(), null, null, null); 268 final AciContainer container = 269 new AciLDAPOperationContainer(operation, e, (ACI_READ | ACI_EXT_OP)); 270 return accessAllowed(container); 271 } 272 273 /** {@inheritDoc} */ 274 @Override 275 public boolean isAllowed(LocalBackendAddOperation operation) 276 throws DirectoryException 277 { 278 AciContainer container = new AciLDAPOperationContainer(operation, ACI_ADD); 279 return isAllowed(container, operation) 280 // LDAP add needs a verify ACI syntax step in case any 281 // "aci" attribute types are being added. 282 && verifySyntax(operation.getEntryToAdd(), operation, container.getClientDN()); 283 } 284 285 /** {@inheritDoc} */ 286 @Override 287 public boolean isAllowed(BindOperation bindOperation) 288 { 289 // Not planned to be implemented. 290 return true; 291 } 292 293 294 295 /** 296 * Check access on compare operations. Note that the attribute type is 297 * unavailable at this time, so this method partially parses the raw 298 * attribute string to get the base attribute type. Options are 299 * ignored. 300 * 301 * @param operation 302 * The compare operation to check access on. 303 * @return True if access is allowed. 304 */ 305 @Override 306 public boolean isAllowed(LocalBackendCompareOperation operation) 307 { 308 AciContainer container = 309 new AciLDAPOperationContainer(operation, ACI_COMPARE); 310 311 String baseName; 312 String rawAttributeType = operation.getRawAttributeType(); 313 int semicolonPosition = rawAttributeType.indexOf(';'); 314 if (semicolonPosition > 0) 315 { 316 baseName = 317 toLowerCase(rawAttributeType.substring(0, semicolonPosition)); 318 } 319 else 320 { 321 baseName = toLowerCase(rawAttributeType); 322 } 323 324 container.setCurrentAttributeType(getAttributeType(baseName)); 325 container.setCurrentAttributeValue(operation.getAssertionValue()); 326 return isAllowed(container, operation); 327 } 328 329 330 331 /** 332 * Check access on delete operations. 333 * 334 * @param operation 335 * The delete operation to check access on. 336 * @return True if access is allowed. 337 */ 338 @Override 339 public boolean isAllowed(LocalBackendDeleteOperation operation) 340 { 341 AciContainer container = 342 new AciLDAPOperationContainer(operation, ACI_DELETE); 343 return isAllowed(container, operation); 344 } 345 346 347 348 /** 349 * Checks access on a modifyDN operation. 350 * 351 * @param operation 352 * The modifyDN operation to check access on. 353 * @return True if access is allowed. 354 */ 355 @Override 356 public boolean isAllowed(ModifyDNOperation operation) 357 { 358 if (skipAccessCheck(operation)) 359 { 360 return true; 361 } 362 363 final RDN oldRDN = operation.getOriginalEntry().getName().rdn(); 364 final RDN newRDN = operation.getNewRDN(); 365 final DN newSuperiorDN = operation.getNewSuperior(); 366 367 // If this is a modifyDN move to a new superior, then check if the 368 // superior DN has import access. 369 if (newSuperiorDN != null 370 && !aciCheckSuperiorEntry(newSuperiorDN, operation)) 371 { 372 return false; 373 } 374 375 // Perform the RDN access checks. 376 boolean rdnChangesAllowed = aciCheckRDNs(operation, oldRDN, newRDN); 377 378 // If this is a modifyDN move to a new superior, then check if the 379 // original entry DN has export access. 380 if (rdnChangesAllowed && newSuperiorDN != null) 381 { 382 AciContainer container = new AciLDAPOperationContainer( 383 operation, ACI_EXPORT, operation.getOriginalEntry()); 384 if (!oldRDN.equals(newRDN)) 385 { 386 // The RDNs are not equal, skip the proxy check since it was 387 // already performed in the aciCheckRDNs call above. 388 container.setSeenEntry(true); 389 } 390 return accessAllowed(container); 391 } 392 return rdnChangesAllowed; 393 } 394 395 /** {@inheritDoc} */ 396 @Override 397 public boolean isAllowed(LocalBackendModifyOperation operation) 398 throws DirectoryException 399 { 400 AciContainer container = new AciLDAPOperationContainer(operation, ACI_NULL); 401 return aciCheckMods(container, operation, skipAccessCheck(operation)); 402 } 403 404 /** {@inheritDoc} */ 405 @Override 406 public boolean isAllowed(SearchOperation searchOperation) 407 { 408 // Not planned to be implemented. 409 return true; 410 } 411 412 /** {@inheritDoc} */ 413 @Override 414 public boolean isAllowed(Operation operation, Entry entry, 415 SearchFilter filter) throws DirectoryException 416 { 417 if (skipAccessCheck(operation)) 418 { 419 return true; 420 } 421 422 AciContainer container = 423 new AciLDAPOperationContainer(operation, ACI_READ, entry); 424 return testFilter(container, filter); 425 } 426 427 /** {@inheritDoc} */ 428 @Override 429 public boolean mayProxy(Entry proxyUser, Entry proxiedUser, Operation op) 430 { 431 if (skipAccessCheck(proxyUser)) 432 { 433 return true; 434 } 435 436 final AuthenticationInfo authInfo = 437 new AuthenticationInfo(proxyUser, DirectoryServer.isRootDN(proxyUser 438 .getName())); 439 final AciContainer container = 440 new AciLDAPOperationContainer(op, proxiedUser, authInfo, ACI_PROXY); 441 return accessAllowedEntry(container); 442 } 443 444 /** {@inheritDoc} */ 445 @Override 446 public boolean maySend(DN dn, Operation operation, SearchResultReference reference) 447 { 448 if (skipAccessCheck(operation)) 449 { 450 return true; 451 } 452 453 // Load the values, a bind rule might want to evaluate them. 454 final AttributeBuilder builder = new AttributeBuilder(refAttrType, ATTR_REFERRAL_URL); 455 builder.addAllStrings(reference.getReferralURLs()); 456 457 final Entry e = new Entry(dn, null, null, null); 458 e.addAttribute(builder.toAttribute(), null); 459 final SearchResultEntry se = new SearchResultEntry(e); 460 final AciContainer container = 461 new AciLDAPOperationContainer(operation, ACI_READ, se); 462 container.setCurrentAttributeType(refAttrType); 463 return accessAllowed(container); 464 } 465 466 /** {@inheritDoc} */ 467 @Override 468 public boolean maySend(Operation operation, SearchResultEntry entry) 469 { 470 if (skipAccessCheck(operation)) 471 { 472 return true; 473 } 474 475 AciContainer container = 476 new AciLDAPOperationContainer(operation, ACI_SEARCH, entry); 477 478 // Pre/post read controls are associated with other types of operation. 479 if (operation instanceof SearchOperation) 480 { 481 try 482 { 483 if (!testFilter(container, ((SearchOperation) operation).getFilter())) 484 { 485 return false; 486 } 487 } 488 catch (DirectoryException ex) 489 { 490 return false; 491 } 492 } 493 494 container.clearEvalAttributes(ACI_NULL); 495 container.setRights(ACI_READ); 496 497 if (!accessAllowedEntry(container)) 498 { 499 return false; 500 } 501 502 if (!container.hasEvalUserAttributes()) 503 { 504 operation.setAttachment(ALL_USER_ATTRS_MATCHED, ALL_USER_ATTRS_MATCHED); 505 } 506 if (!container.hasEvalOpAttributes()) 507 { 508 operation.setAttachment(ALL_OP_ATTRS_MATCHED, ALL_OP_ATTRS_MATCHED); 509 } 510 511 return true; 512 } 513 514 515 516 /** 517 * Check access using the specified container. This container will 518 * have all of the information to gather applicable ACIs and perform 519 * evaluation on them. 520 * 521 * @param container 522 * An ACI operation container which has all of the 523 * information needed to check access. 524 * @return True if access is allowed. 525 */ 526 boolean accessAllowed(AciContainer container) 527 { 528 DN dn = container.getResourceDN(); 529 // For ACI_WRITE_ADD and ACI_WRITE_DELETE set the ACI_WRITE 530 // right. 531 if (container.hasRights(ACI_WRITE_ADD) 532 || container.hasRights(ACI_WRITE_DELETE)) 533 { 534 container.setRights(container.getRights() | ACI_WRITE); 535 } 536 // Check if the ACI_SELF right needs to be set (selfwrite right). 537 // Only done if the right is ACI_WRITE, an attribute value is set 538 // and that attribute value is a DN. 539 if (container.getCurrentAttributeValue() != null 540 && container.hasRights(ACI_WRITE) 541 && isAttributeDN(container.getCurrentAttributeType())) 542 { 543 String dnString = null; 544 try 545 { 546 dnString = container.getCurrentAttributeValue().toString(); 547 DN tmpDN = DN.valueOf(dnString); 548 // Have a valid DN, compare to clientDN to see if the ACI_SELF 549 // right should be set. 550 if (tmpDN.equals(container.getClientDN())) 551 { 552 container.setRights(container.getRights() | ACI_SELF); 553 } 554 } 555 catch (LocalizedIllegalArgumentException ex) 556 { 557 // Log a message and keep going. 558 logger.warn(WARN_ACI_NOT_VALID_DN, dnString); 559 } 560 } 561 562 // First get all allowed candidate ACIs. 563 List<Aci> candidates = aciList.getCandidateAcis(dn); 564 /* 565 * Create an applicable list of ACIs by target matching each 566 * candidate ACI against the container's target match view. 567 */ 568 createApplicableList(candidates, container); 569 // Evaluate the applicable list. 570 final boolean ret = testApplicableLists(container); 571 // Build summary string if doing geteffectiverights eval. 572 if (container.isGetEffectiveRightsEval()) 573 { 574 container.setEvalSummary( 575 AciEffectiveRights.createSummary(container, ret)); 576 } 577 return ret; 578 } 579 580 581 582 /* 583 * TODO Evaluate performance of this method. TODO Evaluate security 584 * concerns of this method. Logic from this method taken almost 585 * directly from DS6 implementation. I find the work done in the 586 * accessAllowedEntry method, particularly with regard to the entry 587 * test evaluation, to be very confusing and potentially pretty 588 * inefficient. I'm also concerned that the "return "true" inside the 589 * for loop could potentially allow access when it should be denied. 590 */ 591 592 /** 593 * Check if access is allowed on an entry. Access is checked by 594 * iterating through each attribute of an entry, starting with the 595 * "objectclass" attribute type. If access is allowed on the entry 596 * based on one of it's attribute types, then a possible second access 597 * check is performed. This second check is only performed if an entry 598 * test ACI was found during the earlier successful access check. An 599 * entry test ACI has no "targetattrs" keyword, so allowing access 600 * based on an attribute type only would be incorrect. 601 * 602 * @param container 603 * ACI search container containing all of the information 604 * needed to check access. 605 * @return True if access is allowed. 606 */ 607 boolean accessAllowedEntry(AciContainer container) 608 { 609 // set flag that specifies this is the first attribute evaluated 610 // in the entry 611 container.setIsFirstAttribute(true); 612 for (AttributeType attrType : getAllAttrs(container.getResourceEntry())) 613 { 614 /* 615 * Check if access is allowed. If true, then check to see if an 616 * entry test rule was found (no targetattrs) during target match 617 * evaluation. If such a rule was found, set the current attribute 618 * type to "null" and check access again so that rule is applied. 619 */ 620 container.setCurrentAttributeType(attrType); 621 if (accessAllowed(container)) 622 { 623 if (container.hasEntryTestRule()) 624 { 625 container.setCurrentAttributeType(null); 626 if (!accessAllowed(container) && container.isDenyEval()) 627 { 628 /* 629 * If we failed because of a deny permission-bind rule, we need to 630 * stop and return false. 631 * If we failed because there was no explicit allow rule, then we 632 * grant implicit access to the entry. 633 */ 634 return false; 635 } 636 } 637 return true; 638 } 639 } 640 return false; 641 } 642 643 644 645 /** 646 * Performs an access check against all of the attributes of an entry. The 647 * attributes that fail access are removed from the entry. This method 648 * performs the processing needed for the filterEntry method processing. 649 * 650 * @param container 651 * The search or compare container which has all of the information 652 * needed to filter the attributes for this entry. 653 * @param filteredEntry 654 * The partially filtered search result entry being returned to the 655 * client. 656 */ 657 private void filterEntry(AciContainer container, Entry filteredEntry) 658 { 659 for (AttributeType attrType : getAllAttrs(filteredEntry)) 660 { 661 if (container.hasAllUserAttributes() && !attrType.isOperational()) 662 { 663 continue; 664 } 665 if (container.hasAllOpAttributes() && attrType.isOperational()) 666 { 667 continue; 668 } 669 container.setCurrentAttributeType(attrType); 670 if (!accessAllowed(container)) 671 { 672 filteredEntry.removeAttribute(attrType); 673 } 674 } 675 } 676 677 678 679 /** 680 * Checks to see if a LDAP modification is allowed access. 681 * 682 * @param container 683 * The structure containing the LDAP modifications 684 * @param operation 685 * The operation to check modify privileges on. operation to 686 * check and the evaluation context to apply the check 687 * against. 688 * @param skipAccessCheck 689 * True if access checking should be skipped. 690 * @return True if access is allowed. 691 * @throws DirectoryException 692 * If a modified ACI could not be decoded. 693 */ 694 private boolean aciCheckMods(AciContainer container, 695 LocalBackendModifyOperation operation, boolean skipAccessCheck) 696 throws DirectoryException 697 { 698 Entry resourceEntry = container.getResourceEntry(); 699 DN dn = resourceEntry.getName(); 700 List<Modification> modifications = operation.getModifications(); 701 702 for (Modification m : modifications) 703 { 704 Attribute modAttr = m.getAttribute(); 705 AttributeType modAttrType = modAttr.getAttributeDescription().getAttributeType(); 706 707 if (modAttrType.equals(aciType) 708 /* 709 * Check that the operation has modify privileges if it contains 710 * an "aci" attribute type. 711 */ 712 && !operation.getClientConnection().hasPrivilege( 713 Privilege.MODIFY_ACL, operation)) 714 { 715 logger.debug(INFO_ACI_MODIFY_FAILED_PRIVILEGE, container.getResourceDN(), container.getClientDN()); 716 return false; 717 } 718 // This access check handles the case where all attributes of this 719 // type are being replaced or deleted. If only a subset is being 720 // deleted than this access check is skipped. 721 ModificationType modType = m.getModificationType(); 722 if (((modType == ModificationType.DELETE && modAttr.isEmpty()) 723 || modType == ModificationType.REPLACE 724 || modType == ModificationType.INCREMENT) 725 /* 726 * Check if we have rights to delete all values of an attribute 727 * type in the resource entry. 728 */ 729 && resourceEntry.hasAttribute(modAttrType)) 730 { 731 container.setCurrentAttributeType(modAttrType); 732 for (Attribute a : resourceEntry.getAttribute(modAttr.getAttributeDescription())) 733 { 734 for (ByteString v : a) 735 { 736 container.setCurrentAttributeValue(v); 737 container.setRights(ACI_WRITE_DELETE); 738 if (!skipAccessCheck && !accessAllowed(container)) 739 { 740 return false; 741 } 742 } 743 } 744 } 745 746 if (!modAttr.isEmpty()) 747 { 748 for (ByteString v : modAttr) 749 { 750 container.setCurrentAttributeType(modAttrType); 751 switch (m.getModificationType().asEnum()) 752 { 753 case ADD: 754 case REPLACE: 755 container.setCurrentAttributeValue(v); 756 container.setRights(ACI_WRITE_ADD); 757 if (!skipAccessCheck && !accessAllowed(container)) 758 { 759 return false; 760 } 761 break; 762 case DELETE: 763 container.setCurrentAttributeValue(v); 764 container.setRights(ACI_WRITE_DELETE); 765 if (!skipAccessCheck && !accessAllowed(container)) 766 { 767 return false; 768 } 769 break; 770 case INCREMENT: 771 Entry modifiedEntry = operation.getModifiedEntry(); 772 for (Attribute attr : modifiedEntry.getAttribute(modAttr.getAttributeDescription())) 773 { 774 for (ByteString val : attr) 775 { 776 container.setCurrentAttributeValue(val); 777 container.setRights(ACI_WRITE_ADD); 778 if (!skipAccessCheck && !accessAllowed(container)) 779 { 780 return false; 781 } 782 } 783 } 784 break; 785 } 786 /* 787 * Check if the modification type has an "aci" attribute type. 788 * If so, check the syntax of that attribute value. Fail the 789 * the operation if the syntax check fails. 790 */ 791 if (modAttrType.equals(aciType) 792 || modAttrType.equals(globalAciType)) 793 { 794 try 795 { 796 // A global ACI needs a NULL DN, not the DN of the 797 // modification. 798 if (modAttrType.equals(globalAciType)) 799 { 800 dn = DN.rootDN(); 801 } 802 // validate ACI syntax 803 Aci.decode(v, dn); 804 } 805 catch (AciException ex) 806 { 807 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 808 WARN_ACI_MODIFY_FAILED_DECODE.get(dn, ex.getMessage())); 809 } 810 } 811 } 812 } 813 } 814 return true; 815 } 816 817 818 819 /** 820 * Perform all needed RDN checks for the modifyDN operation. The old RDN is 821 * not equal to the new RDN. The access checks are: 822 * <ul> 823 * <li>Verify WRITE access to the original entry.</li> 824 * <li>Verify WRITE_ADD access on each RDN component of the new RDN. The 825 * WRITE_ADD access is used because this access could be restricted by the 826 * targattrfilters keyword.</li> 827 * <li>If the deleteOLDRDN flag is set, verify WRITE_DELETE access on the old 828 * RDN. The WRITE_DELETE access is used because this access could be 829 * restricted by the targattrfilters keyword. 830 * <li> 831 * </ul> 832 * 833 * @param operation 834 * The ModifyDN operation class containing information to check 835 * access on. 836 * @param oldRDN 837 * The old RDN component. 838 * @param newRDN 839 * The new RDN component. 840 * @return True if access is allowed. 841 */ 842 private boolean aciCheckRDNs(ModifyDNOperation operation, 843 RDN oldRDN, RDN newRDN) 844 { 845 AciContainer container = 846 new AciLDAPOperationContainer(operation, ACI_WRITE, operation 847 .getOriginalEntry()); 848 if (!accessAllowed(container)) 849 { 850 return false; 851 } 852 853 boolean ret = checkRDN(ACI_WRITE_ADD, newRDN, container); 854 if (ret && operation.deleteOldRDN()) 855 { 856 ret = checkRDN(ACI_WRITE_DELETE, oldRDN, container); 857 } 858 return ret; 859 } 860 861 862 863 /** 864 * Check access on the new superior entry if it exists. If superiordn is null, 865 * the entry does not exist or the DN cannot be locked then false is returned. 866 * 867 * @param superiorDN 868 * The DN of the new superior entry. 869 * @param op 870 * The modifyDN operation to check access on. 871 * @return True if access is granted to the new superior entry. 872 */ 873 private boolean aciCheckSuperiorEntry(DN superiorDN, ModifyDNOperation op) 874 { 875 try 876 { 877 Entry superiorEntry = DirectoryServer.getEntry(superiorDN); 878 if (superiorEntry != null) 879 { 880 AciContainer container = 881 new AciLDAPOperationContainer(op, ACI_IMPORT, superiorEntry); 882 return accessAllowed(container); 883 } 884 return false; 885 } 886 catch (DirectoryException ex) 887 { 888 return false; 889 } 890 } 891 892 893 894 /** 895 * Check access on each attribute-value pair component of the 896 * specified RDN. There may be more than one attribute-value pair if 897 * the RDN is multi-valued. 898 * 899 * @param right 900 * The access right to check for. 901 * @param rdn 902 * The RDN to examine the attribute-value pairs of. 903 * @param container 904 * The container containing the information needed to 905 * evaluate the specified RDN. 906 * @return True if access is allowed for all attribute-value pairs. 907 */ 908 private boolean checkRDN(int right, RDN rdn, AciContainer container) 909 { 910 container.setRights(right); 911 for (AVA ava : rdn) 912 { 913 container.setCurrentAttributeType(ava.getAttributeType()); 914 container.setCurrentAttributeValue(ava.getAttributeValue()); 915 if (!accessAllowed(container)) 916 { 917 return false; 918 } 919 } 920 return true; 921 } 922 923 924 925 /** 926 * Creates the allow and deny ACI lists based on the provided target 927 * match context. These lists are stored in the evaluation context. 928 * 929 * @param candidates 930 * List of all possible ACI candidates. 931 * @param targetMatchCtx 932 * Target matching context to use for testing each ACI. 933 */ 934 private void createApplicableList(List<Aci> candidates, 935 AciTargetMatchContext targetMatchCtx) 936 { 937 List<Aci> denys = new LinkedList<>(); 938 List<Aci> allows = new LinkedList<>(); 939 for (Aci aci : candidates) 940 { 941 if (Aci.isApplicable(aci, targetMatchCtx)) 942 { 943 if (aci.hasAccessType(EnumAccessType.DENY)) 944 { 945 denys.add(aci); 946 } 947 if (aci.hasAccessType(EnumAccessType.ALLOW)) 948 { 949 allows.add(aci); 950 } 951 } 952 if (targetMatchCtx.getTargAttrFiltersMatch()) 953 { 954 targetMatchCtx.setTargAttrFiltersMatch(false); 955 } 956 } 957 targetMatchCtx.setAllowList(allows); 958 targetMatchCtx.setDenyList(denys); 959 } 960 961 962 963 /** 964 * Gathers all of the attribute types in an entry along with the 965 * "objectclass" attribute type in a List. The "objectclass" attribute 966 * is added to the list first so it is evaluated first. 967 * 968 * @param e 969 * Entry to gather the attributes for. 970 * @return List containing the attribute types. 971 */ 972 private List<AttributeType> getAllAttrs(Entry e) 973 { 974 List<AttributeType> typeList = new LinkedList<>(); 975 /* 976 * When a search is not all attributes returned, the "objectclass" 977 * attribute type is missing from the entry. 978 */ 979 final Attribute attr = e.getObjectClassAttribute(); 980 if (attr != null) 981 { 982 typeList.add(attr.getAttributeDescription().getAttributeType()); 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 = entry.getOperationalAttribute(AttributeDescription.create(aciType)); 1314 for (Attribute attribute : attributeList) 1315 { 1316 for (ByteString value : attribute) 1317 { 1318 try 1319 { 1320 // validate ACI syntax 1321 Aci.decode(value, entry.getName()); 1322 } 1323 catch (AciException ex) 1324 { 1325 throw new DirectoryException( 1326 ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1327 WARN_ACI_ADD_FAILED_DECODE.get(entry.getName(), ex.getMessage())); 1328 } 1329 } 1330 } 1331 } 1332 return true; 1333 } 1334}