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.ByteString; 027import org.forgerock.opendj.ldap.DN; 028import org.forgerock.opendj.ldap.ResultCode; 029import org.forgerock.opendj.ldap.schema.AttributeType; 030import org.opends.server.api.ClientConnection; 031import org.opends.server.protocols.ldap.LDAPAttribute; 032import org.opends.server.protocols.ldap.LDAPResultCode; 033import org.opends.server.types.AbstractOperation; 034import org.opends.server.types.Attribute; 035import org.opends.server.types.AttributeBuilder; 036import org.opends.server.types.CancelResult; 037import org.opends.server.types.CanceledOperationException; 038import org.opends.server.types.Control; 039import org.opends.server.types.Entry; 040import org.opends.server.types.LDAPException; 041import org.opends.server.types.ObjectClass; 042import org.opends.server.types.Operation; 043import org.opends.server.types.OperationType; 044import org.opends.server.types.RawAttribute; 045import org.opends.server.types.operation.PostResponseAddOperation; 046import org.opends.server.types.operation.PreParseAddOperation; 047import org.opends.server.workflowelement.localbackend.LocalBackendAddOperation; 048 049import static org.opends.messages.CoreMessages.*; 050import static org.opends.server.config.ConfigConstants.*; 051import static org.opends.server.core.DirectoryServer.*; 052import static org.opends.server.loggers.AccessLogger.*; 053import static org.opends.server.util.CollectionUtils.*; 054import static org.opends.server.util.StaticUtils.*; 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 AttributeType attrType = attr.getAttributeDescription().getAttributeType(); 293 294 // If the attribute type is marked "NO-USER-MODIFICATION" then fail 295 // unless this is an internal operation or is related to 296 // synchronization in some way. 297 if (attrType.isNoUserModification() 298 && !isInternalOperation() 299 && !isSynchronizationOperation()) 300 { 301 throw new LDAPException(LDAPResultCode.UNWILLING_TO_PERFORM, 302 ERR_ADD_ATTR_IS_NO_USER_MOD.get(entryDN, attr.getName())); 303 } 304 305 if(attrType.getSyntax().isBEREncodingRequired()) 306 { 307 if(!attr.hasOption("binary")) 308 { 309 //A binary option wasn't provided by the client so add it. 310 AttributeBuilder builder = new AttributeBuilder(attr); 311 builder.setOption("binary"); 312 attr = builder.toAttribute(); 313 } 314 } 315 else if (attr.hasOption("binary")) 316 { 317 // binary option is not honored for non-BER-encodable attributes. 318 throw new LDAPException(LDAPResultCode.UNDEFINED_ATTRIBUTE_TYPE, 319 ERR_ADD_ATTR_IS_INVALID_OPTION.get(entryDN, attr.getName())); 320 } 321 322 if (attrType.isObjectClass()) 323 { 324 for (ByteString os : a.getValues()) 325 { 326 String ocName = os.toString(); 327 ObjectClass oc = 328 DirectoryServer.getObjectClass(toLowerCase(ocName)); 329 if (oc == null) 330 { 331 oc = DirectoryServer.getDefaultObjectClass(ocName); 332 } 333 334 objectClasses.put(oc,ocName); 335 } 336 } 337 else if (attrType.isOperational()) 338 { 339 List<Attribute> attrs = operationalAttributes.get(attrType); 340 if (attrs == null) 341 { 342 attrs = new ArrayList<>(1); 343 operationalAttributes.put(attrType, attrs); 344 } 345 attrs.add(attr); 346 } 347 else 348 { 349 List<Attribute> attrs = userAttributes.get(attrType); 350 if (attrs == null) 351 { 352 attrs = newArrayList(attr); 353 userAttributes.put(attrType, attrs); 354 } 355 else 356 { 357 // Check to see if any of the existing attributes in the list 358 // have the same set of options. If so, then add the values 359 // to that attribute. 360 boolean attributeSeen = false; 361 for (int i = 0; i < attrs.size(); i++) { 362 Attribute ea = attrs.get(i); 363 if (ea.getAttributeDescription().equals(attr.getAttributeDescription())) 364 { 365 AttributeBuilder builder = new AttributeBuilder(ea); 366 builder.addAll(attr); 367 attrs.set(i, builder.toAttribute()); 368 attributeSeen = true; 369 } 370 } 371 372 if (!attributeSeen) 373 { 374 // This is the first occurrence of the attribute and options. 375 attrs.add(attr); 376 } 377 } 378 } 379 } 380 catch (LDAPException le) 381 { 382 setResultCode(ResultCode.valueOf(le.getResultCode())); 383 appendErrorMessage(le.getMessageObject()); 384 385 objectClasses = null; 386 userAttributes = null; 387 operationalAttributes = null; 388 ldapError = true; 389 return; 390 } 391 } 392 } 393 } 394 395 @Override 396 public final void setAttribute(AttributeType attributeType, 397 List<Attribute> attributeList) 398 { 399 Map<AttributeType, List<Attribute>> attributes = 400 getAttributes(attributeType.isOperational()); 401 if (attributeList == null || attributeList.isEmpty()) 402 { 403 attributes.remove(attributeType); 404 } 405 else 406 { 407 attributes.put(attributeType, attributeList); 408 } 409 } 410 411 @Override 412 public final void removeAttribute(AttributeType attributeType) 413 { 414 getAttributes(attributeType.isOperational()).remove(attributeType); 415 } 416 417 private Map<AttributeType, List<Attribute>> getAttributes(boolean isOperational) 418 { 419 if (isOperational) 420 { 421 return operationalAttributes; 422 } 423 return userAttributes; 424 } 425 426 @Override 427 public final OperationType getOperationType() 428 { 429 // Note that no debugging will be done in this method because it is a likely 430 // candidate for being called by the logging subsystem. 431 432 return OperationType.ADD; 433 } 434 435 @Override 436 public DN getProxiedAuthorizationDN() 437 { 438 return proxiedAuthorizationDN; 439 } 440 441 @Override 442 public final ArrayList<Control> getResponseControls() 443 { 444 return responseControls; 445 } 446 447 @Override 448 public final void addResponseControl(Control control) 449 { 450 responseControls.add(control); 451 } 452 453 @Override 454 public final void removeResponseControl(Control control) 455 { 456 responseControls.remove(control); 457 } 458 459 @Override 460 public final void toString(StringBuilder buffer) 461 { 462 buffer.append("AddOperation(connID="); 463 buffer.append(clientConnection.getConnectionID()); 464 buffer.append(", opID="); 465 buffer.append(operationID); 466 buffer.append(", dn="); 467 buffer.append(rawEntryDN); 468 buffer.append(")"); 469 } 470 471 @Override 472 public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN) 473 { 474 this.proxiedAuthorizationDN = proxiedAuthorizationDN; 475 } 476 477 @Override 478 public final void run() 479 { 480 setResultCode(ResultCode.UNDEFINED); 481 482 // Start the processing timer. 483 setProcessingStartTime(); 484 485 logAddRequest(this); 486 487 // This flag is set to true as soon as a workflow has been executed. 488 boolean workflowExecuted = false; 489 try 490 { 491 // Check for and handle a request to cancel this operation. 492 checkIfCanceled(false); 493 494 // Invoke the pre-parse add plugins. 495 if (!processOperationResult(getPluginConfigManager().invokePreParseAddPlugins(this))) 496 { 497 return; 498 } 499 500 // Check for and handle a request to cancel this operation. 501 checkIfCanceled(false); 502 503 // Process the entry DN and set of attributes to convert them from their 504 // raw forms as provided by the client to the forms required for the rest 505 // of the add processing. 506 DN entryDN = getEntryDN(); 507 if (entryDN == null){ 508 return; 509 } 510 511 workflowExecuted = execute(this, entryDN); 512 } 513 catch(CanceledOperationException coe) 514 { 515 logger.traceException(coe); 516 517 setResultCode(ResultCode.CANCELLED); 518 cancelResult = new CancelResult(ResultCode.CANCELLED, null); 519 520 appendErrorMessage(coe.getCancelRequest().getCancelReason()); 521 } 522 finally 523 { 524 // Stop the processing timer. 525 setProcessingStopTime(); 526 527 // Log the add response message. 528 logAddResponse(this); 529 530 if(cancelRequest == null || cancelResult == null || 531 cancelResult.getResultCode() != ResultCode.CANCELLED || 532 cancelRequest.notifyOriginalRequestor() || 533 DirectoryServer.notifyAbandonedOperations()) 534 { 535 clientConnection.sendResponse(this); 536 } 537 538 539 // Invoke the post-response callbacks. 540 if (workflowExecuted) { 541 invokePostResponseCallbacks(); 542 } 543 544 // Invoke the post-response add plugins. 545 invokePostResponsePlugins(workflowExecuted); 546 547 // If no cancel result, set it 548 if(cancelResult == null) 549 { 550 cancelResult = new CancelResult(ResultCode.TOO_LATE, null); 551 } 552 } 553 } 554 555 556 /** 557 * Invokes the post response plugins. If a workflow has been executed 558 * then invoke the post response plugins provided by the workflow 559 * elements of the workflow, otherwise invoke the post response plugins 560 * that have been registered with the current operation. 561 * 562 * @param workflowExecuted <code>true</code> if a workflow has been executed 563 */ 564 @SuppressWarnings({ "unchecked", "rawtypes" }) 565 private void invokePostResponsePlugins(boolean workflowExecuted) 566 { 567 // Invoke the post response plugins 568 if (workflowExecuted) 569 { 570 // Invoke the post response plugins that have been registered by 571 // the workflow elements 572 List<LocalBackendAddOperation> localOperations = 573 (List) getAttachment(Operation.LOCALBACKENDOPERATIONS); 574 575 if (localOperations != null) 576 { 577 for (LocalBackendAddOperation localOp : localOperations) 578 { 579 getPluginConfigManager().invokePostResponseAddPlugins(localOp); 580 } 581 } 582 } 583 else 584 { 585 // Invoke the post response plugins that have been registered with 586 // the current operation 587 getPluginConfigManager().invokePostResponseAddPlugins(this); 588 } 589 } 590 591 @Override 592 public void updateOperationErrMsgAndResCode() 593 { 594 DN entryDN = getEntryDN(); 595 DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN); 596 if (parentDN == null) 597 { 598 // Either this entry is a suffix or doesn't belong in the directory. 599 if (DirectoryServer.isNamingContext(entryDN)) 600 { 601 // This is fine. This entry is one of the configured suffixes. 602 return; 603 } 604 if (entryDN.isRootDN()) 605 { 606 // This is not fine. The root DSE cannot be added. 607 setResultCode(ResultCode.UNWILLING_TO_PERFORM); 608 appendErrorMessage(ERR_ADD_CANNOT_ADD_ROOT_DSE.get()); 609 return; 610 } 611 // The entry doesn't have a parent but isn't a suffix. This is not allowed. 612 setResultCode(ResultCode.NO_SUCH_OBJECT); 613 appendErrorMessage(ERR_ADD_ENTRY_NOT_SUFFIX.get(entryDN)); 614 return; 615 } 616 // The suffix does not exist 617 setResultCode(ResultCode.NO_SUCH_OBJECT); 618 appendErrorMessage(ERR_ADD_ENTRY_UNKNOWN_SUFFIX.get(entryDN)); 619 } 620 621 622 /** 623 * {@inheritDoc} 624 * 625 * This method always returns null. 626 */ 627 @Override 628 public Entry getEntryToAdd() 629 { 630 return null; 631 } 632}