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-2009 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 */ 017package org.opends.server.workflowelement.localbackend; 018 019import java.util.List; 020import java.util.concurrent.atomic.AtomicBoolean; 021 022import org.forgerock.i18n.LocalizableMessage; 023import org.forgerock.i18n.LocalizableMessageDescriptor.Arg2; 024import org.forgerock.i18n.slf4j.LocalizedLogger; 025import org.forgerock.opendj.ldap.AttributeDescription; 026import org.forgerock.opendj.ldap.ByteString; 027import org.forgerock.opendj.ldap.DN; 028import org.forgerock.opendj.ldap.ResultCode; 029import org.opends.server.api.AccessControlHandler; 030import org.opends.server.api.Backend; 031import org.opends.server.api.ClientConnection; 032import org.opends.server.controls.LDAPAssertionRequestControl; 033import org.opends.server.core.AccessControlConfigManager; 034import org.opends.server.core.CompareOperation; 035import org.opends.server.core.CompareOperationWrapper; 036import org.opends.server.core.DirectoryServer; 037import org.opends.server.types.Attribute; 038import org.opends.server.types.CanceledOperationException; 039import org.opends.server.types.Control; 040import org.opends.server.types.DirectoryException; 041import org.opends.server.types.Entry; 042import org.opends.server.types.Privilege; 043import org.opends.server.types.SearchFilter; 044import org.opends.server.types.operation.PostOperationCompareOperation; 045import org.opends.server.types.operation.PostResponseCompareOperation; 046import org.opends.server.types.operation.PreOperationCompareOperation; 047 048import static org.opends.messages.CoreMessages.*; 049import static org.opends.server.core.DirectoryServer.*; 050import static org.opends.server.types.AbstractOperation.*; 051import static org.opends.server.util.ServerConstants.*; 052import static org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement.*; 053 054/** 055 * This class defines an operation that may be used to determine whether a 056 * specified entry in the Directory Server contains a given attribute-value pair. 057 */ 058public class LocalBackendCompareOperation 059 extends CompareOperationWrapper 060 implements PreOperationCompareOperation, PostOperationCompareOperation, 061 PostResponseCompareOperation 062{ 063 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 064 065 /** The backend in which the comparison is to be performed. */ 066 private Backend<?> backend; 067 /** The client connection for this operation. */ 068 private ClientConnection clientConnection; 069 /** The DN of the entry to compare. */ 070 private DN entryDN; 071 /** The entry to be compared. */ 072 private Entry entry; 073 074 075 076 /** 077 * Creates a new compare operation based on the provided compare operation. 078 * 079 * @param compare the compare operation 080 */ 081 public LocalBackendCompareOperation(CompareOperation compare) 082 { 083 super(compare); 084 LocalBackendWorkflowElement.attachLocalOperation (compare, this); 085 } 086 087 088 089 /** 090 * Retrieves the entry to target with the compare operation. 091 * 092 * @return The entry to target with the compare operation, or 093 * <CODE>null</CODE> if the entry is not yet available. 094 */ 095 @Override 096 public Entry getEntryToCompare() 097 { 098 return entry; 099 } 100 101 102 103 /** 104 * Process this compare operation in a local backend. 105 * 106 * @param wfe 107 * The local backend work-flow element. 108 * @throws CanceledOperationException 109 * if this operation should be cancelled 110 */ 111 public void processLocalCompare(LocalBackendWorkflowElement wfe) 112 throws CanceledOperationException 113 { 114 this.backend = wfe.getBackend(); 115 116 clientConnection = getClientConnection(); 117 118 // Check for a request to cancel this operation. 119 checkIfCanceled(false); 120 121 try 122 { 123 AtomicBoolean executePostOpPlugins = new AtomicBoolean(false); 124 processCompare(executePostOpPlugins); 125 126 // Check for a request to cancel this operation. 127 checkIfCanceled(false); 128 129 // Invoke the post-operation compare plugins. 130 if (executePostOpPlugins.get()) 131 { 132 processOperationResult(this, getPluginConfigManager().invokePostOperationComparePlugins(this)); 133 } 134 } 135 finally 136 { 137 LocalBackendWorkflowElement.filterNonDisclosableMatchedDN(this); 138 } 139 } 140 141 private void processCompare(AtomicBoolean executePostOpPlugins) 142 throws CanceledOperationException 143 { 144 // Process the entry DN to convert it from the raw form to the form 145 // required for the rest of the compare processing. 146 entryDN = getEntryDN(); 147 if (entryDN == null) 148 { 149 return; 150 } 151 152 153 // If the target entry is in the server configuration, then make sure the 154 // requester has the CONFIG_READ privilege. 155 if (DirectoryServer.getConfigHandler().handlesEntry(entryDN) 156 && !clientConnection.hasPrivilege(Privilege.CONFIG_READ, this)) 157 { 158 appendErrorMessage(ERR_COMPARE_CONFIG_INSUFFICIENT_PRIVILEGES.get()); 159 setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS); 160 return; 161 } 162 163 // Check for a request to cancel this operation. 164 checkIfCanceled(false); 165 166 try 167 { 168 // Get the entry. If it does not exist, then fail. 169 try 170 { 171 entry = DirectoryServer.getEntry(entryDN); 172 if (entry == null) 173 { 174 setResultCode(ResultCode.NO_SUCH_OBJECT); 175 appendErrorMessage(ERR_COMPARE_NO_SUCH_ENTRY.get(entryDN)); 176 177 // See if one of the entry's ancestors exists. 178 setMatchedDN(findMatchedDN(entryDN)); 179 return; 180 } 181 } 182 catch (DirectoryException de) 183 { 184 logger.traceException(de); 185 186 setResultCodeAndMessageNoInfoDisclosure(entry, entryDN, 187 de.getResultCode(), de.getMessageObject()); 188 return; 189 } 190 191 // Check to see if there are any controls in the request. If so, then 192 // see if there is any special processing required. 193 handleRequestControls(); 194 195 196 // Check to see if the client has permission to perform the 197 // compare. 198 199 // FIXME: for now assume that this will check all permission 200 // pertinent to the operation. This includes proxy authorization 201 // and any other controls specified. 202 203 // FIXME: earlier checks to see if the entry already exists may 204 // have already exposed sensitive information to the client. 205 try 206 { 207 if (!getAccessControlHandler().isAllowed(this)) 208 { 209 setResultCodeAndMessageNoInfoDisclosure(entry, entryDN, 210 ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 211 ERR_COMPARE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(entryDN)); 212 return; 213 } 214 } 215 catch (DirectoryException e) 216 { 217 setResultCode(e.getResultCode()); 218 appendErrorMessage(e.getMessageObject()); 219 return; 220 } 221 222 // Check for a request to cancel this operation. 223 checkIfCanceled(false); 224 225 226 // Invoke the pre-operation compare plugins. 227 executePostOpPlugins.set(true); 228 if (!processOperationResult(this, getPluginConfigManager().invokePreOperationComparePlugins(this))) 229 { 230 return; 231 } 232 233 // Actually perform the compare operation. 234 AttributeDescription attrDesc = getAttributeDescription(); 235 List<Attribute> attrList = entry.getAttribute(attrDesc); 236 if (attrList.isEmpty()) 237 { 238 setResultCode(ResultCode.NO_SUCH_ATTRIBUTE); 239 Arg2<Object, Object> errorMsg = attrDesc.hasOptions() 240 ? WARN_COMPARE_OP_NO_SUCH_ATTR 241 : WARN_COMPARE_OP_NO_SUCH_ATTR_WITH_OPTIONS; 242 appendErrorMessage(errorMsg.get(entryDN, getRawAttributeType())); 243 } 244 else 245 { 246 ByteString value = getAssertionValue(); 247 setResultCode(matchExists(attrList, value)); 248 } 249 } 250 catch (DirectoryException de) 251 { 252 logger.traceException(de); 253 setResponseData(de); 254 } 255 } 256 257 private ResultCode matchExists(List<Attribute> attrList, ByteString value) 258 { 259 for (Attribute a : attrList) 260 { 261 if (a.contains(value)) 262 { 263 return ResultCode.COMPARE_TRUE; 264 } 265 } 266 return ResultCode.COMPARE_FALSE; 267 } 268 269 private DirectoryException newDirectoryException(Entry entry, 270 ResultCode resultCode, LocalizableMessage message) throws DirectoryException 271 { 272 return LocalBackendWorkflowElement.newDirectoryException(this, entry, null, 273 resultCode, message, ResultCode.NO_SUCH_OBJECT, 274 ERR_COMPARE_NO_SUCH_ENTRY.get(entryDN)); 275 } 276 277 private void setResultCodeAndMessageNoInfoDisclosure(Entry entry, DN entryDN, 278 ResultCode realResultCode, LocalizableMessage realMessage) throws DirectoryException 279 { 280 LocalBackendWorkflowElement.setResultCodeAndMessageNoInfoDisclosure(this, 281 entry, entryDN, realResultCode, realMessage, ResultCode.NO_SUCH_OBJECT, 282 ERR_COMPARE_NO_SUCH_ENTRY.get(entryDN)); 283 } 284 285 /** 286 * Performs any processing required for the controls included in the request. 287 * 288 * @throws DirectoryException If a problem occurs that should prevent the 289 * operation from succeeding. 290 */ 291 private void handleRequestControls() throws DirectoryException 292 { 293 LocalBackendWorkflowElement.evaluateProxyAuthControls(this); 294 LocalBackendWorkflowElement.removeAllDisallowedControls(entryDN, this); 295 296 for (Control c : getRequestControls()) 297 { 298 final String oid = c.getOID(); 299 300 if (OID_LDAP_ASSERTION.equals(oid)) 301 { 302 LDAPAssertionRequestControl assertControl = getRequestControl(LDAPAssertionRequestControl.DECODER); 303 304 SearchFilter filter; 305 try 306 { 307 filter = assertControl.getSearchFilter(); 308 } 309 catch (DirectoryException de) 310 { 311 logger.traceException(de); 312 313 throw newDirectoryException(entry, de.getResultCode(), 314 ERR_COMPARE_CANNOT_PROCESS_ASSERTION_FILTER.get(entryDN, de.getMessageObject())); 315 } 316 317 // Check if the current user has permission to make this determination. 318 if (!getAccessControlHandler().isAllowed(this, entry, filter)) 319 { 320 throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 321 ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid)); 322 } 323 324 try 325 { 326 if (!filter.matchesEntry(entry)) 327 { 328 throw newDirectoryException(entry, ResultCode.ASSERTION_FAILED, ERR_COMPARE_ASSERTION_FAILED.get(entryDN)); 329 } 330 } 331 catch (DirectoryException de) 332 { 333 if (de.getResultCode() == ResultCode.ASSERTION_FAILED) 334 { 335 throw de; 336 } 337 338 logger.traceException(de); 339 340 throw newDirectoryException(entry, de.getResultCode(), 341 ERR_COMPARE_CANNOT_PROCESS_ASSERTION_FILTER.get(entryDN, de.getMessageObject())); 342 } 343 } 344 else if (LocalBackendWorkflowElement.isProxyAuthzControl(oid)) 345 { 346 continue; 347 } 348 else if (c.isCritical() && (backend == null || !backend.supportsControl(oid))) 349 { 350 throw new DirectoryException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION, 351 ERR_COMPARE_UNSUPPORTED_CRITICAL_CONTROL.get(entryDN, oid)); 352 } 353 } 354 } 355 356 private AccessControlHandler<?> getAccessControlHandler() 357 { 358 return AccessControlConfigManager.getInstance().getAccessControlHandler(); 359 } 360}