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 2007-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2013-2016 ForgeRock AS. 016 */ 017package org.opends.server.core; 018 019import java.util.ArrayList; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023 024import org.forgerock.i18n.LocalizedIllegalArgumentException; 025import org.forgerock.i18n.slf4j.LocalizedLogger; 026import org.forgerock.opendj.ldap.AttributeDescription; 027import org.forgerock.opendj.ldap.ByteString; 028import org.forgerock.opendj.ldap.DN; 029import org.forgerock.opendj.ldap.ResultCode; 030import org.forgerock.opendj.ldap.schema.AttributeType; 031import org.opends.server.api.ClientConnection; 032import org.opends.server.protocols.ldap.LDAPAttribute; 033import org.opends.server.protocols.ldap.LDAPResultCode; 034import org.opends.server.types.AbstractOperation; 035import org.opends.server.types.Attribute; 036import org.opends.server.types.AttributeBuilder; 037import org.opends.server.types.CancelResult; 038import org.opends.server.types.CanceledOperationException; 039import org.opends.server.types.Control; 040import org.opends.server.types.Entry; 041import org.opends.server.types.LDAPException; 042import org.forgerock.opendj.ldap.schema.ObjectClass; 043import org.opends.server.types.Operation; 044import org.opends.server.types.OperationType; 045import org.opends.server.types.RawAttribute; 046import org.opends.server.types.operation.PostResponseAddOperation; 047import org.opends.server.types.operation.PreParseAddOperation; 048import org.opends.server.workflowelement.localbackend.LocalBackendAddOperation; 049 050import static org.opends.messages.CoreMessages.*; 051import static org.opends.server.config.ConfigConstants.*; 052import static org.opends.server.core.DirectoryServer.*; 053import static org.opends.server.loggers.AccessLogger.*; 054import static org.opends.server.util.CollectionUtils.*; 055import static org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement.*; 056 057/** 058 * This class defines an operation that may be used to add a new entry to the 059 * Directory Server. 060 */ 061public class AddOperationBasis 062 extends AbstractOperation 063 implements PreParseAddOperation, AddOperation, PostResponseAddOperation 064{ 065 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 066 067 /** The set of response controls to send to the client. */ 068 private final ArrayList<Control> responseControls = new ArrayList<>(); 069 070 /** The raw, unprocessed entry DN as provided in the request. This may or may not be a valid DN. */ 071 private ByteString rawEntryDN; 072 /** The processed DN of the entry to add. */ 073 private DN entryDN; 074 /** The proxied authorization target DN for this operation. */ 075 private DN proxiedAuthorizationDN; 076 077 /** 078 * The set of attributes (including the objectclass attribute) in a raw, 079 * unprocessed form as provided in the request. One or more of these 080 * attributes may be invalid. 081 */ 082 private List<RawAttribute> rawAttributes; 083 /** The set of operational attributes for the entry to add. */ 084 private Map<AttributeType,List<Attribute>> operationalAttributes; 085 /** The set of user attributes for the entry to add. */ 086 private Map<AttributeType,List<Attribute>> userAttributes; 087 /** The set of objectclasses for the entry to add. */ 088 private Map<ObjectClass,String> objectClasses; 089 090 /** The flag indicates if an LDAP error was reported. */ 091 private boolean ldapError; 092 093 /** 094 * Creates a new add operation with the provided information. 095 * 096 * @param clientConnection The client connection with which this operation 097 * is associated. 098 * @param operationID The operation ID for this operation. 099 * @param messageID The message ID of the request with which this 100 * operation is associated. 101 * @param requestControls The set of controls included in the request. 102 * @param rawEntryDN The raw DN of the entry to add from the client 103 * request. This may or may not be a valid DN. 104 * @param rawAttributes The raw set of attributes from the client 105 * request (including the objectclass attribute). 106 * This may contain invalid attributes. 107 */ 108 public AddOperationBasis(ClientConnection clientConnection, long operationID, 109 int messageID, List<Control> requestControls, 110 ByteString rawEntryDN, List<RawAttribute> rawAttributes) 111 { 112 super(clientConnection, operationID, messageID, requestControls); 113 114 115 this.rawEntryDN = rawEntryDN; 116 this.rawAttributes = rawAttributes; 117 118 entryDN = null; 119 userAttributes = null; 120 operationalAttributes = null; 121 objectClasses = null; 122 } 123 124 125 126 /** 127 * Creates a new add operation with the provided information. 128 * 129 * @param clientConnection The client connection with which this 130 * operation is associated. 131 * @param operationID The operation ID for this operation. 132 * @param messageID The message ID of the request with which 133 * this operation is associated. 134 * @param requestControls The set of controls included in the request. 135 * @param entryDN The DN for the entry. 136 * @param objectClasses The set of objectclasses for the entry. 137 * @param userAttributes The set of user attributes for the entry. 138 * @param operationalAttributes The set of operational attributes for the 139 * entry. 140 */ 141 public AddOperationBasis(ClientConnection clientConnection, long operationID, 142 int messageID, List<Control> requestControls, 143 DN entryDN, Map<ObjectClass,String> objectClasses, 144 Map<AttributeType,List<Attribute>> userAttributes, 145 Map<AttributeType,List<Attribute>> operationalAttributes) 146 { 147 super(clientConnection, operationID, messageID, requestControls); 148 149 150 this.entryDN = entryDN; 151 this.objectClasses = objectClasses; 152 this.userAttributes = userAttributes; 153 this.operationalAttributes = operationalAttributes; 154 155 rawEntryDN = ByteString.valueOfUtf8(entryDN.toString()); 156 157 ArrayList<String> values = new ArrayList<>(objectClasses.values()); 158 rawAttributes = new ArrayList<>(); 159 rawAttributes.add(new LDAPAttribute(ATTR_OBJECTCLASS, values)); 160 addAll(rawAttributes, userAttributes); 161 addAll(rawAttributes, operationalAttributes); 162 } 163 164 private void addAll(List<RawAttribute> rawAttributes, Map<AttributeType, List<Attribute>> attributesToAdd) 165 { 166 for (List<Attribute> attrList : attributesToAdd.values()) 167 { 168 for (Attribute a : attrList) 169 { 170 rawAttributes.add(new LDAPAttribute(a)); 171 } 172 } 173 } 174 175 @Override 176 public final ByteString getRawEntryDN() 177 { 178 return rawEntryDN; 179 } 180 181 @Override 182 public final void setRawEntryDN(ByteString rawEntryDN) 183 { 184 this.rawEntryDN = rawEntryDN; 185 186 entryDN = null; 187 } 188 189 @Override 190 public final DN getEntryDN() 191 { 192 try 193 { 194 if (entryDN == null) 195 { 196 entryDN = DN.valueOf(rawEntryDN); 197 } 198 } 199 catch (LocalizedIllegalArgumentException e) 200 { 201 logger.traceException(e); 202 setResultCode(ResultCode.INVALID_DN_SYNTAX); 203 appendErrorMessage(e.getMessageObject()); 204 } 205 return entryDN; 206 } 207 208 @Override 209 public final List<RawAttribute> getRawAttributes() 210 { 211 return rawAttributes; 212 } 213 214 @Override 215 public final void addRawAttribute(RawAttribute rawAttribute) 216 { 217 rawAttributes.add(rawAttribute); 218 219 objectClasses = null; 220 userAttributes = null; 221 operationalAttributes = null; 222 } 223 224 @Override 225 public final void setRawAttributes(List<RawAttribute> rawAttributes) 226 { 227 this.rawAttributes = rawAttributes; 228 229 objectClasses = null; 230 userAttributes = null; 231 operationalAttributes = null; 232 } 233 234 @Override 235 public final Map<ObjectClass,String> getObjectClasses() 236 { 237 if (objectClasses == null){ 238 computeObjectClassesAndAttributes(); 239 } 240 return objectClasses; 241 } 242 243 @Override 244 public final void addObjectClass(ObjectClass objectClass, String name) 245 { 246 objectClasses.put(objectClass, name); 247 } 248 249 @Override 250 public final void removeObjectClass(ObjectClass objectClass) 251 { 252 objectClasses.remove(objectClass); 253 } 254 255 @Override 256 public final Map<AttributeType,List<Attribute>> getUserAttributes() 257 { 258 if (userAttributes == null){ 259 computeObjectClassesAndAttributes(); 260 } 261 return userAttributes; 262 } 263 264 @Override 265 public final Map<AttributeType,List<Attribute>> getOperationalAttributes() 266 { 267 if (operationalAttributes == null){ 268 computeObjectClassesAndAttributes(); 269 } 270 return operationalAttributes; 271 } 272 273 /** 274 * Build the objectclasses, the user attributes and the operational attributes 275 * if there are not already computed. 276 */ 277 private final void computeObjectClassesAndAttributes() 278 { 279 if (!ldapError 280 && (objectClasses == null || userAttributes == null 281 || operationalAttributes == null)) 282 { 283 objectClasses = new HashMap<>(); 284 userAttributes = new HashMap<>(); 285 operationalAttributes = new HashMap<>(); 286 287 for (RawAttribute a : rawAttributes) 288 { 289 try 290 { 291 Attribute attr = a.toAttribute(); 292 AttributeDescription attrDesc = attr.getAttributeDescription(); 293 AttributeType attrType = attrDesc.getAttributeType(); 294 295 // If the attribute type is marked "NO-USER-MODIFICATION" then fail 296 // unless this is an internal operation or is related to 297 // synchronization in some way. 298 if (attrType.isNoUserModification() 299 && !isInternalOperation() 300 && !isSynchronizationOperation()) 301 { 302 throw new LDAPException(LDAPResultCode.UNWILLING_TO_PERFORM, 303 ERR_ADD_ATTR_IS_NO_USER_MOD.get(entryDN, attrDesc)); 304 } 305 306 boolean hasBinaryOption = attrDesc.hasOption("binary"); 307 if (attrType.getSyntax().isBEREncodingRequired()) 308 { 309 if (!hasBinaryOption) 310 { 311 //A binary option wasn't provided by the client so add it. 312 AttributeBuilder builder = new AttributeBuilder(attr); 313 builder.setOption("binary"); 314 attr = builder.toAttribute(); 315 } 316 } 317 else if (hasBinaryOption) 318 { 319 // binary option is not honored for non-BER-encodable attributes. 320 throw new LDAPException(LDAPResultCode.UNDEFINED_ATTRIBUTE_TYPE, 321 ERR_ADD_ATTR_IS_INVALID_OPTION.get(entryDN, attrDesc)); 322 } 323 324 if (attrType.isObjectClass()) 325 { 326 for (ByteString os : a.getValues()) 327 { 328 String ocName = os.toString(); 329 objectClasses.put(getSchema().getObjectClass(ocName), ocName); 330 } 331 } 332 else if (attrType.isOperational()) 333 { 334 List<Attribute> attrs = operationalAttributes.get(attrType); 335 if (attrs == null) 336 { 337 attrs = new ArrayList<>(1); 338 operationalAttributes.put(attrType, attrs); 339 } 340 attrs.add(attr); 341 } 342 else 343 { 344 List<Attribute> attrs = userAttributes.get(attrType); 345 if (attrs == null) 346 { 347 attrs = newArrayList(attr); 348 userAttributes.put(attrType, attrs); 349 } 350 else 351 { 352 // Check to see if any of the existing attributes in the list 353 // have the same set of options. If so, then add the values 354 // to that attribute. 355 boolean attributeSeen = false; 356 for (int i = 0; i < attrs.size(); i++) { 357 Attribute ea = attrs.get(i); 358 if (ea.getAttributeDescription().equals(attrDesc)) 359 { 360 AttributeBuilder builder = new AttributeBuilder(ea); 361 builder.addAll(attr); 362 attrs.set(i, builder.toAttribute()); 363 attributeSeen = true; 364 } 365 } 366 367 if (!attributeSeen) 368 { 369 // This is the first occurrence of the attribute and options. 370 attrs.add(attr); 371 } 372 } 373 } 374 } 375 catch (LDAPException le) 376 { 377 setResultCode(ResultCode.valueOf(le.getResultCode())); 378 appendErrorMessage(le.getMessageObject()); 379 380 objectClasses = null; 381 userAttributes = null; 382 operationalAttributes = null; 383 ldapError = true; 384 return; 385 } 386 } 387 } 388 } 389 390 @Override 391 public final void setAttribute(AttributeType attributeType, 392 List<Attribute> attributeList) 393 { 394 Map<AttributeType, List<Attribute>> attributes = 395 getAttributes(attributeType.isOperational()); 396 if (attributeList == null || attributeList.isEmpty()) 397 { 398 attributes.remove(attributeType); 399 } 400 else 401 { 402 attributes.put(attributeType, attributeList); 403 } 404 } 405 406 @Override 407 public final void removeAttribute(AttributeType attributeType) 408 { 409 getAttributes(attributeType.isOperational()).remove(attributeType); 410 } 411 412 private Map<AttributeType, List<Attribute>> getAttributes(boolean isOperational) 413 { 414 if (isOperational) 415 { 416 return operationalAttributes; 417 } 418 return userAttributes; 419 } 420 421 @Override 422 public final OperationType getOperationType() 423 { 424 // Note that no debugging will be done in this method because it is a likely 425 // candidate for being called by the logging subsystem. 426 427 return OperationType.ADD; 428 } 429 430 @Override 431 public DN getProxiedAuthorizationDN() 432 { 433 return proxiedAuthorizationDN; 434 } 435 436 @Override 437 public final ArrayList<Control> getResponseControls() 438 { 439 return responseControls; 440 } 441 442 @Override 443 public final void addResponseControl(Control control) 444 { 445 responseControls.add(control); 446 } 447 448 @Override 449 public final void removeResponseControl(Control control) 450 { 451 responseControls.remove(control); 452 } 453 454 @Override 455 public final void toString(StringBuilder buffer) 456 { 457 buffer.append("AddOperation(connID="); 458 buffer.append(clientConnection.getConnectionID()); 459 buffer.append(", opID="); 460 buffer.append(operationID); 461 buffer.append(", dn="); 462 buffer.append(rawEntryDN); 463 buffer.append(")"); 464 } 465 466 @Override 467 public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN) 468 { 469 this.proxiedAuthorizationDN = proxiedAuthorizationDN; 470 } 471 472 @Override 473 public final void run() 474 { 475 setResultCode(ResultCode.UNDEFINED); 476 477 // Start the processing timer. 478 setProcessingStartTime(); 479 480 logAddRequest(this); 481 482 // This flag is set to true as soon as a workflow has been executed. 483 boolean workflowExecuted = false; 484 try 485 { 486 // Check for and handle a request to cancel this operation. 487 checkIfCanceled(false); 488 489 // Invoke the pre-parse add plugins. 490 if (!processOperationResult(getPluginConfigManager().invokePreParseAddPlugins(this))) 491 { 492 return; 493 } 494 495 // Check for and handle a request to cancel this operation. 496 checkIfCanceled(false); 497 498 // Process the entry DN and set of attributes to convert them from their 499 // raw forms as provided by the client to the forms required for the rest 500 // of the add processing. 501 DN entryDN = getEntryDN(); 502 if (entryDN == null){ 503 return; 504 } 505 506 workflowExecuted = execute(this, entryDN); 507 } 508 catch(CanceledOperationException coe) 509 { 510 logger.traceException(coe); 511 512 setResultCode(ResultCode.CANCELLED); 513 cancelResult = new CancelResult(ResultCode.CANCELLED, null); 514 515 appendErrorMessage(coe.getCancelRequest().getCancelReason()); 516 } 517 finally 518 { 519 // Stop the processing timer. 520 setProcessingStopTime(); 521 522 // Log the add response message. 523 logAddResponse(this); 524 525 if(cancelRequest == null || cancelResult == null || 526 cancelResult.getResultCode() != ResultCode.CANCELLED || 527 cancelRequest.notifyOriginalRequestor() || 528 DirectoryServer.notifyAbandonedOperations()) 529 { 530 clientConnection.sendResponse(this); 531 } 532 533 534 // Invoke the post-response callbacks. 535 if (workflowExecuted) { 536 invokePostResponseCallbacks(); 537 } 538 539 // Invoke the post-response add plugins. 540 invokePostResponsePlugins(workflowExecuted); 541 542 // If no cancel result, set it 543 if(cancelResult == null) 544 { 545 cancelResult = new CancelResult(ResultCode.TOO_LATE, null); 546 } 547 } 548 } 549 550 551 /** 552 * Invokes the post response plugins. If a workflow has been executed 553 * then invoke the post response plugins provided by the workflow 554 * elements of the workflow, otherwise invoke the post response plugins 555 * that have been registered with the current operation. 556 * 557 * @param workflowExecuted <code>true</code> if a workflow has been executed 558 */ 559 @SuppressWarnings({ "unchecked", "rawtypes" }) 560 private void invokePostResponsePlugins(boolean workflowExecuted) 561 { 562 // Invoke the post response plugins 563 if (workflowExecuted) 564 { 565 // Invoke the post response plugins that have been registered by 566 // the workflow elements 567 List<LocalBackendAddOperation> localOperations = 568 (List) getAttachment(Operation.LOCALBACKENDOPERATIONS); 569 570 if (localOperations != null) 571 { 572 for (LocalBackendAddOperation localOp : localOperations) 573 { 574 getPluginConfigManager().invokePostResponseAddPlugins(localOp); 575 } 576 } 577 } 578 else 579 { 580 // Invoke the post response plugins that have been registered with 581 // the current operation 582 getPluginConfigManager().invokePostResponseAddPlugins(this); 583 } 584 } 585 586 @Override 587 public void updateOperationErrMsgAndResCode() 588 { 589 DN entryDN = getEntryDN(); 590 DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN); 591 if (parentDN == null) 592 { 593 // Either this entry is a suffix or doesn't belong in the directory. 594 if (DirectoryServer.isNamingContext(entryDN)) 595 { 596 // This is fine. This entry is one of the configured suffixes. 597 return; 598 } 599 if (entryDN.isRootDN()) 600 { 601 // This is not fine. The root DSE cannot be added. 602 setResultCode(ResultCode.UNWILLING_TO_PERFORM); 603 appendErrorMessage(ERR_ADD_CANNOT_ADD_ROOT_DSE.get()); 604 return; 605 } 606 // The entry doesn't have a parent but isn't a suffix. This is not allowed. 607 setResultCode(ResultCode.NO_SUCH_OBJECT); 608 appendErrorMessage(ERR_ADD_ENTRY_NOT_SUFFIX.get(entryDN)); 609 return; 610 } 611 // The suffix does not exist 612 setResultCode(ResultCode.NO_SUCH_OBJECT); 613 appendErrorMessage(ERR_ADD_ENTRY_UNKNOWN_SUFFIX.get(entryDN)); 614 } 615 616 617 /** 618 * {@inheritDoc} 619 * 620 * This method always returns null. 621 */ 622 @Override 623 public Entry getEntryToAdd() 624 { 625 return null; 626 } 627}