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