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 2006-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 */ 017package org.opends.server.core; 018 019import static org.opends.messages.CoreMessages.*; 020import static org.opends.server.core.DirectoryServer.*; 021import static org.opends.server.loggers.AccessLogger.*; 022import static org.opends.server.util.ServerConstants.*; 023 024import java.util.ArrayList; 025import java.util.Iterator; 026import java.util.List; 027 028import org.forgerock.i18n.slf4j.LocalizedLogger; 029import org.forgerock.opendj.ldap.ByteString; 030import org.forgerock.opendj.ldap.ResultCode; 031import org.opends.server.api.AccessControlHandler; 032import org.opends.server.api.ClientConnection; 033import org.opends.server.api.ExtendedOperationHandler; 034import org.opends.server.types.AbstractOperation; 035import org.opends.server.types.CancelResult; 036import org.opends.server.types.CanceledOperationException; 037import org.opends.server.types.Control; 038import org.forgerock.opendj.ldap.DN; 039import org.opends.server.types.DirectoryException; 040import org.opends.server.types.OperationType; 041import org.opends.server.types.operation.PostOperationExtendedOperation; 042import org.opends.server.types.operation.PostResponseExtendedOperation; 043import org.opends.server.types.operation.PreOperationExtendedOperation; 044import org.opends.server.types.operation.PreParseExtendedOperation; 045 046/** This class defines an extended operation, which can perform virtually any kind of task. */ 047public class ExtendedOperationBasis 048 extends AbstractOperation 049 implements ExtendedOperation, 050 PreParseExtendedOperation, 051 PreOperationExtendedOperation, 052 PostOperationExtendedOperation, 053 PostResponseExtendedOperation 054{ 055 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 056 057 /** The value for the request associated with this extended operation. */ 058 private ByteString requestValue; 059 060 /** The value for the response associated with this extended operation. */ 061 private ByteString responseValue; 062 063 /** The set of response controls for this extended operation. */ 064 private List<Control> responseControls; 065 066 /** The OID for the request associated with this extended operation. */ 067 private String requestOID; 068 069 /** The OID for the response associated with this extended operation. */ 070 private String responseOID; 071 072 /** 073 * Creates a new extended operation with the provided information. 074 * 075 * @param clientConnection The client connection with which this operation 076 * is associated. 077 * @param operationID The operation ID for this operation. 078 * @param messageID The message ID of the request with which this 079 * operation is associated. 080 * @param requestControls The set of controls included in the request. 081 * @param requestOID The OID for the request associated with this 082 * extended operation. 083 * @param requestValue The value for the request associated with this 084 * extended operation. 085 */ 086 public ExtendedOperationBasis(ClientConnection clientConnection, 087 long operationID, 088 int messageID, List<Control> requestControls, 089 String requestOID, ByteString requestValue) 090 { 091 super(clientConnection, operationID, messageID, requestControls); 092 093 this.requestOID = requestOID; 094 this.requestValue = requestValue; 095 096 responseOID = null; 097 responseValue = null; 098 responseControls = new ArrayList<>(); 099 cancelRequest = null; 100 101 if (requestOID.equals(OID_CANCEL_REQUEST)) 102 { 103 cancelResult = new CancelResult(ResultCode.CANNOT_CANCEL, 104 ERR_CANNOT_CANCEL_CANCEL.get()); 105 } 106 if(requestOID.equals(OID_START_TLS_REQUEST)) 107 { 108 cancelResult = new CancelResult(ResultCode.CANNOT_CANCEL, 109 ERR_CANNOT_CANCEL_START_TLS.get()); 110 } 111 } 112 113 @Override 114 public final String getRequestOID() 115 { 116 return requestOID; 117 } 118 119 /** 120 * Specifies the OID for the request associated with this extended operation. 121 * This should only be called by pre-parse plugins. 122 * 123 * @param requestOID The OID for the request associated with this extended 124 * operation. 125 */ 126 @Override 127 public final void setRequestOID(String requestOID) 128 { 129 this.requestOID = requestOID; 130 } 131 132 @Override 133 public DN getProxiedAuthorizationDN() 134 { 135 return null; 136 } 137 138 @Override 139 public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN) 140 { 141 } 142 143 @Override 144 public final ByteString getRequestValue() 145 { 146 return requestValue; 147 } 148 149 /** 150 * Specifies the value for the request associated with this extended 151 * operation. This should only be called by pre-parse plugins. 152 * 153 * @param requestValue The value for the request associated with this 154 * extended operation. 155 */ 156 @Override 157 public final void setRequestValue(ByteString requestValue) 158 { 159 this.requestValue = requestValue; 160 } 161 162 @Override 163 public final String getResponseOID() 164 { 165 return responseOID; 166 } 167 168 @Override 169 public final void setResponseOID(String responseOID) 170 { 171 this.responseOID = responseOID; 172 } 173 174 @Override 175 public final ByteString getResponseValue() 176 { 177 return responseValue; 178 } 179 180 @Override 181 public final void setResponseValue(ByteString responseValue) 182 { 183 this.responseValue = responseValue; 184 } 185 186 @Override 187 public final OperationType getOperationType() 188 { 189 // Note that no debugging will be done in this method because it is a likely 190 // candidate for being called by the logging subsystem. 191 return OperationType.EXTENDED; 192 } 193 194 @Override 195 public final List<Control> getResponseControls() 196 { 197 return responseControls; 198 } 199 200 @Override 201 public final void addResponseControl(Control control) 202 { 203 responseControls.add(control); 204 } 205 206 @Override 207 public final void removeResponseControl(Control control) 208 { 209 responseControls.remove(control); 210 } 211 212 /** 213 * Performs the work of actually processing this operation. This 214 * should include all processing for the operation, including 215 * invoking plugins, logging messages, performing access control, 216 * managing synchronization, and any other work that might need to 217 * be done in the course of processing. 218 */ 219 @Override 220 public final void run() 221 { 222 setResultCode(ResultCode.UNDEFINED); 223 224 // Start the processing timer. 225 setProcessingStartTime(); 226 227 logExtendedRequest(this); 228 229 try 230 { 231 // Check for and handle a request to cancel this operation. 232 checkIfCanceled(false); 233 234 // Invoke the pre-parse extended plugins. 235 if (!processOperationResult(getPluginConfigManager().invokePreParseExtendedPlugins(this))) 236 { 237 return; 238 } 239 240 checkIfCanceled(false); 241 242 // Get the extended operation handler for the request OID. If there is 243 // none, then fail. 244 ExtendedOperationHandler<?> handler = 245 DirectoryServer.getExtendedOperationHandler(requestOID); 246 if (handler == null) 247 { 248 setResultCode(ResultCode.UNWILLING_TO_PERFORM); 249 appendErrorMessage(ERR_EXTENDED_NO_HANDLER.get(requestOID)); 250 return; 251 } 252 253 // Look at the controls included in the request and ensure that all 254 // critical controls are supported by the handler. 255 for (Iterator<Control> iter = getRequestControls().iterator(); iter.hasNext();) 256 { 257 final Control c = iter.next(); 258 try 259 { 260 if (!getAccessControlHandler().isAllowed(getAuthorizationDN(), this, c)) 261 { 262 // As per RFC 4511 4.1.11. 263 if (c.isCritical()) 264 { 265 setResultCode(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION); 266 appendErrorMessage(ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(c.getOID())); 267 } 268 else 269 { 270 // We don't want to process this non-critical control, so remove it. 271 iter.remove(); 272 continue; 273 } 274 } 275 } 276 catch (DirectoryException e) 277 { 278 setResultCode(e.getResultCode()); 279 appendErrorMessage(e.getMessageObject()); 280 return; 281 } 282 283 if (!c.isCritical()) 284 { 285 // The control isn't critical, so we don't care if it's supported 286 // or not. 287 } 288 else if (!handler.supportsControl(c.getOID())) 289 { 290 setResultCode(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION); 291 appendErrorMessage(ERR_EXTENDED_UNSUPPORTED_CRITICAL_CONTROL.get(requestOID, c.getOID())); 292 return; 293 } 294 } 295 296 // Check to see if the client has permission to perform the 297 // extended operation. 298 299 // FIXME: for now assume that this will check all permission 300 // pertinent to the operation. This includes proxy authorization 301 // and any other controls specified. 302 try 303 { 304 if (!getAccessControlHandler().isAllowed(this)) 305 { 306 setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS); 307 appendErrorMessage(ERR_EXTENDED_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(requestOID)); 308 return; 309 } 310 } 311 catch (DirectoryException e) 312 { 313 setResultCode(e.getResultCode()); 314 appendErrorMessage(e.getMessageObject()); 315 return; 316 } 317 318 try 319 { 320 // Invoke the pre-operation extended plugins. 321 if (!processOperationResult(getPluginConfigManager().invokePreOperationExtendedPlugins(this))) 322 { 323 return; 324 } 325 326 checkIfCanceled(false); 327 328 // Actually perform the processing for this operation. 329 handler.processExtendedOperation(this); 330 } 331 finally 332 { 333 getPluginConfigManager().invokePostOperationExtendedPlugins(this); 334 } 335 } 336 catch(CanceledOperationException coe) 337 { 338 logger.traceException(coe); 339 340 setResultCode(ResultCode.CANCELLED); 341 cancelResult = new CancelResult(ResultCode.CANCELLED, null); 342 343 appendErrorMessage(coe.getCancelRequest().getCancelReason()); 344 } 345 finally 346 { 347 // Stop the processing timer. 348 setProcessingStopTime(); 349 350 // Log the extended response. 351 logExtendedResponse(this); 352 353 // Send the response to the client. 354 if(cancelRequest == null || cancelResult == null || 355 cancelResult.getResultCode() != ResultCode.CANCELLED || 356 cancelRequest.notifyOriginalRequestor() || 357 DirectoryServer.notifyAbandonedOperations()) 358 { 359 clientConnection.sendResponse(this); 360 } 361 362 if(requestOID.equals(OID_START_TLS_REQUEST)) 363 { 364 clientConnection.finishStartTLS(); 365 } 366 367 // Invoke the post-response extended plugins. 368 getPluginConfigManager().invokePostResponseExtendedPlugins(this); 369 370 // If no cancel result, set it 371 if(cancelResult == null) 372 { 373 cancelResult = new CancelResult(ResultCode.TOO_LATE, null); 374 } 375 } 376 } 377 378 private AccessControlHandler<?> getAccessControlHandler() 379 { 380 return AccessControlConfigManager.getInstance().getAccessControlHandler(); 381 } 382 383 @Override 384 public final void toString(StringBuilder buffer) 385 { 386 buffer.append("ExtendedOperation(connID="); 387 buffer.append(clientConnection.getConnectionID()); 388 buffer.append(", opID="); 389 buffer.append(operationID); 390 buffer.append(", oid="); 391 buffer.append(requestOID); 392 buffer.append(")"); 393 } 394}