| | |
| | | import java.util.SortedSet; |
| | | import java.util.TreeSet; |
| | | |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | | import org.forgerock.i18n.LocalizedIllegalArgumentException; |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | | import org.forgerock.opendj.config.server.ConfigException; |
| | |
| | | import org.forgerock.opendj.ldap.ModificationType; |
| | | import org.forgerock.opendj.ldap.RDN; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.ldap.SearchScope; |
| | | import org.forgerock.opendj.ldap.schema.AttributeType; |
| | | import org.forgerock.opendj.server.config.server.DseeCompatAccessControlHandlerCfg; |
| | | import org.opends.server.api.AccessControlHandler; |
| | | import org.opends.server.api.Backend; |
| | | import org.opends.server.api.ClientConnection; |
| | | import org.opends.server.backends.pluggable.SuffixContainer; |
| | | import org.opends.server.controls.GetEffectiveRightsRequestControl; |
| | | import org.opends.server.core.BindOperation; |
| | | import org.opends.server.core.ConfigurationBackend; |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.opends.server.core.ExtendedOperation; |
| | | import org.opends.server.core.ModifyDNOperation; |
| | | import org.opends.server.core.SearchOperation; |
| | | import org.opends.server.protocols.internal.InternalClientConnection; |
| | | import org.opends.server.protocols.internal.InternalSearchOperation; |
| | | import org.opends.server.protocols.internal.SearchRequest; |
| | | import org.opends.server.protocols.ldap.LDAPControl; |
| | | import org.opends.server.types.Attribute; |
| | | import org.opends.server.types.AttributeBuilder; |
| | |
| | | import org.opends.server.workflowelement.localbackend.LocalBackendCompareOperation; |
| | | import org.opends.server.workflowelement.localbackend.LocalBackendDeleteOperation; |
| | | import org.opends.server.workflowelement.localbackend.LocalBackendModifyOperation; |
| | | import org.opends.server.workflowelement.localbackend.LocalBackendSearchOperation; |
| | | |
| | | import static org.opends.messages.AccessControlMessages.*; |
| | | import static org.opends.server.authorization.dseecompat.Aci.*; |
| | | import static org.opends.server.authorization.dseecompat.EnumEvalReason.*; |
| | | import static org.opends.server.config.ConfigConstants.*; |
| | | import static org.opends.server.core.DirectoryServer.*; |
| | | import static org.opends.server.protocols.internal.InternalClientConnection.*; |
| | | import static org.opends.server.protocols.internal.Requests.*; |
| | | import static org.opends.server.schema.SchemaConstants.*; |
| | | import static org.opends.server.util.ServerConstants.*; |
| | | import static org.opends.server.util.StaticUtils.*; |
| | | |
| | | /** |
| | | * The AciHandler class performs the main processing for the dseecompat package. |
| | | */ |
| | | /** The AciHandler class performs the main processing for the dseecompat package. */ |
| | | public final class AciHandler extends |
| | | AccessControlHandler<DseeCompatAccessControlHandlerCfg> |
| | | { |
| | |
| | | initStatics(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * We initialize these for each new AciHandler so that we can clear out the |
| | | * stale references that can occur during an in-core restart. |
| | |
| | | // the intializeAccessControlHandler method. |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public void filterEntry(Operation operation, |
| | | SearchResultEntry unfilteredEntry, SearchResultEntry filteredEntry) |
| | |
| | | } |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public void finalizeAccessControlHandler() |
| | | { |
| | |
| | | DirectoryServer.deregisterSupportedControl(OID_GET_EFFECTIVE_RIGHTS); |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public void initializeAccessControlHandler( |
| | | DseeCompatAccessControlHandlerCfg configuration) |
| | |
| | | aciList = new AciList(configurationDN); |
| | | aciListenerMgr = new AciListenerManager(aciList, configurationDN); |
| | | processGlobalAcis(configuration); |
| | | processConfigAcis(); |
| | | DirectoryServer.registerSupportedControl(OID_GET_EFFECTIVE_RIGHTS); |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public boolean isAllowed(DN entryDN, Operation op, Control control) |
| | | throws DirectoryException |
| | |
| | | return true; |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public boolean isAllowed(ExtendedOperation operation) |
| | | { |
| | |
| | | return accessAllowed(container); |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public boolean isAllowed(LocalBackendAddOperation operation) |
| | | throws DirectoryException |
| | |
| | | && verifySyntax(operation.getEntryToAdd(), operation, container.getClientDN()); |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public boolean isAllowed(BindOperation bindOperation) |
| | | { |
| | |
| | | return true; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Check access on compare operations. Note that the attribute type is |
| | | * unavailable at this time, so this method partially parses the raw |
| | |
| | | return isAllowed(container, operation); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Check access on delete operations. |
| | | * |
| | |
| | | return isAllowed(container, operation); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Checks access on a modifyDN operation. |
| | | * |
| | |
| | | return rdnChangesAllowed; |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public boolean isAllowed(LocalBackendModifyOperation operation) |
| | | throws DirectoryException |
| | |
| | | return aciCheckMods(container, operation, skipAccessCheck(operation)); |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public boolean isAllowed(SearchOperation searchOperation) |
| | | { |
| | |
| | | return true; |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public boolean isAllowed(Operation operation, Entry entry, |
| | | SearchFilter filter) throws DirectoryException |
| | |
| | | return testFilter(container, filter); |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public boolean mayProxy(Entry proxyUser, Entry proxiedUser, Operation op) |
| | | { |
| | |
| | | return accessAllowedEntry(container); |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public boolean maySend(DN dn, Operation operation, SearchResultReference reference) |
| | | { |
| | |
| | | return accessAllowed(container); |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public boolean maySend(Operation operation, SearchResultEntry entry) |
| | | { |
| | |
| | | return true; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Check access using the specified container. This container will |
| | | * have all of the information to gather applicable ACIs and perform |
| | |
| | | return ret; |
| | | } |
| | | |
| | | |
| | | |
| | | /* |
| | | * TODO Evaluate performance of this method. TODO Evaluate security |
| | | * concerns of this method. Logic from this method taken almost |
| | |
| | | return false; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Performs an access check against all of the attributes of an entry. The |
| | | * attributes that fail access are removed from the entry. This method |
| | |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Checks to see if a LDAP modification is allowed access. |
| | | * |
| | |
| | | |
| | | if (modAttrType.equals(aciType) |
| | | /* |
| | | * Check that the operation has modify privileges if it contains |
| | | * an "aci" attribute type. |
| | | * Check that the operation has modify privileges if it contains an "aci" attribute type. |
| | | */ |
| | | && !operation.getClientConnection().hasPrivilege( |
| | | Privilege.MODIFY_ACL, operation)) |
| | |
| | | || modType == ModificationType.REPLACE |
| | | || modType == ModificationType.INCREMENT) |
| | | /* |
| | | * Check if we have rights to delete all values of an attribute |
| | | * type in the resource entry. |
| | | * Check if we have rights to delete all values of an attribute type in the resource |
| | | * entry. |
| | | */ |
| | | && resourceEntry.hasAttribute(modAttrType)) |
| | | { |
| | |
| | | return true; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Perform all needed RDN checks for the modifyDN operation. The old RDN is |
| | | * not equal to the new RDN. The access checks are: |
| | |
| | | return ret; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Check access on the new superior entry if it exists. If superiordn is null, |
| | | * the entry does not exist or the DN cannot be locked then false is returned. |
| | |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Check access on each attribute-value pair component of the |
| | | * specified RDN. There may be more than one attribute-value pair if |
| | |
| | | return true; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Creates the allow and deny ACI lists based on the provided target |
| | | * match context. These lists are stored in the evaluation context. |
| | |
| | | targetMatchCtx.setDenyList(denys); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Gathers all of the attribute types in an entry along with the |
| | | * "objectclass" attribute type in a List. The "objectclass" attribute |
| | |
| | | return typeList; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Check access using the accessAllowed method. The LDAP add, compare, |
| | | * modify and delete operations use this function. The other supported |
| | |
| | | return SYNTAX_DN_OID.equals(attribute.getSyntax().getOID()); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Process all ACIs under the "cn=config" naming context and adds them |
| | | * to the ACI list cache. It also logs messages about the number of |
| | | * ACIs added to the cache. This method is called once at startup. It |
| | | * will put the server in lockdown mode if needed. |
| | | * |
| | | * @throws InitializationException |
| | | * If there is an error searching for the ACIs in the naming |
| | | * context. |
| | | */ |
| | | private void processConfigAcis() throws InitializationException |
| | | { |
| | | LinkedList<LocalizableMessage> failedACIMsgs = new LinkedList<>(); |
| | | InternalClientConnection conn = getRootConnection(); |
| | | |
| | | Backend<?> configBackend = DirectoryServer.getBackend(ConfigurationBackend.CONFIG_BACKEND_ID); |
| | | for (DN baseDN : configBackend.getBaseDNs()) |
| | | { |
| | | try |
| | | { |
| | | if (! configBackend.entryExists(baseDN)) |
| | | { |
| | | continue; |
| | | } |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | logger.traceException(e); |
| | | |
| | | // FIXME -- Is there anything that we need to do here? |
| | | continue; |
| | | } |
| | | |
| | | try { |
| | | SearchRequest request = newSearchRequest(baseDN, SearchScope.WHOLE_SUBTREE, "aci=*").addAttribute("aci"); |
| | | InternalSearchOperation internalSearch = |
| | | new InternalSearchOperation(conn, nextOperationID(), nextMessageID(), request); |
| | | LocalBackendSearchOperation localSearch = new LocalBackendSearchOperation(internalSearch); |
| | | |
| | | configBackend.search(localSearch); |
| | | |
| | | if (!internalSearch.getSearchEntries().isEmpty()) |
| | | { |
| | | int validAcis = |
| | | aciList.addAci(internalSearch.getSearchEntries(), failedACIMsgs); |
| | | if (!failedACIMsgs.isEmpty()) |
| | | { |
| | | aciListenerMgr.logMsgsSetLockDownMode(failedACIMsgs); |
| | | } |
| | | logger.debug(INFO_ACI_ADD_LIST_ACIS, validAcis, baseDN); |
| | | } |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | LocalizableMessage message = INFO_ACI_HANDLER_FAIL_PROCESS_ACI.get(); |
| | | throw new InitializationException(message, e); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Process all global ACI attribute types found in the configuration |
| | | * entry and adds them to that ACI list cache. It also logs messages |
| | |
| | | { |
| | | try |
| | | { |
| | | final SortedSet<Aci> globalAcis = new TreeSet<Aci>(); |
| | | final SortedSet<Aci> globalAcis = new TreeSet<>(); |
| | | for (String value : configuration.getGlobalACI()) |
| | | { |
| | | globalAcis.add(Aci.decode(ByteString.valueOfUtf8(value), DN.rootDN())); |
| | |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Check to see if the specified entry has the specified privilege. |
| | | * |
| | |
| | | return ClientConnection.hasPrivilege(e, Privilege.BYPASS_ACL); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Check to see if the client entry has BYPASS_ACL privileges for this |
| | | * operation. |
| | |
| | | Privilege.BYPASS_ACL, operation); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Performs the test of the deny and allow access lists using the |
| | | * provided evaluation context. The deny list is checked first. |
| | |
| | | final EnumEvalResult res = Aci.evaluate(evalCtx, denyAci); |
| | | // Failure could be returned if a system limit is hit or |
| | | // search fails |
| | | if (res.equals(EnumEvalResult.FAIL)) |
| | | if (EnumEvalResult.FAIL.equals(res)) |
| | | { |
| | | evalCtx.setEvaluationResult(EVALUATED_DENY_ACI, denyAci); |
| | | return false; |
| | | } |
| | | else if (res.equals(EnumEvalResult.TRUE)) |
| | | else if (EnumEvalResult.TRUE.equals(res)) |
| | | { |
| | | if (testAndSetTargAttrOperationMatches(evalCtx, denyAci, true)) |
| | | { |
| | |
| | | for (Aci allowAci : evalCtx.getAllowList()) |
| | | { |
| | | final EnumEvalResult res = Aci.evaluate(evalCtx, allowAci); |
| | | if (res.equals(EnumEvalResult.TRUE)) |
| | | if (EnumEvalResult.TRUE.equals(res)) |
| | | { |
| | | if (testAndSetTargAttrOperationMatches(evalCtx, allowAci, false)) |
| | | { |
| | |
| | | return true; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Evaluate an entry to be added to see if it has any "aci" attribute |
| | | * type. If it does, examines each "aci" attribute type value for |