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