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