001/*
002 * Copyright 2012-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2012-2022 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2012-2022 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk.unboundidds.controls;
037
038
039
040import java.util.List;
041import java.util.ArrayList;
042
043import com.unboundid.asn1.ASN1Element;
044import com.unboundid.asn1.ASN1Sequence;
045import com.unboundid.ldap.matchingrules.BooleanMatchingRule;
046import com.unboundid.ldap.matchingrules.OctetStringMatchingRule;
047import com.unboundid.ldap.sdk.Control;
048import com.unboundid.ldap.sdk.AddRequest;
049import com.unboundid.ldap.sdk.Attribute;
050import com.unboundid.ldap.sdk.LDAPException;
051import com.unboundid.ldap.sdk.Modification;
052import com.unboundid.ldap.sdk.ResultCode;
053import com.unboundid.ldif.LDIFModifyChangeRecord;
054import com.unboundid.util.Debug;
055import com.unboundid.util.NotMutable;
056import com.unboundid.util.NotNull;
057import com.unboundid.util.Nullable;
058import com.unboundid.util.StaticUtils;
059import com.unboundid.util.ThreadSafety;
060import com.unboundid.util.ThreadSafetyLevel;
061
062import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
063
064
065
066/**
067 * This class provides a request control which may be included in an add request
068 * to indicate that the contents of the resulting entry should come not from the
069 * data of the add request itself but instead from a soft-deleted entry.  This
070 * can be used to recover an entry that was previously removed by a delete
071 * request containing the {@link SoftDeleteRequestControl}.
072 * <BR>
073 * <BLOCKQUOTE>
074 *   <B>NOTE:</B>  This class, and other classes within the
075 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
076 *   supported for use against Ping Identity, UnboundID, and
077 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
078 *   for proprietary functionality or for external specifications that are not
079 *   considered stable or mature enough to be guaranteed to work in an
080 *   interoperable way with other types of LDAP servers.
081 * </BLOCKQUOTE>
082 * <BR>
083 * The criticality for this control should always be {@code TRUE}.  The
084 * criticality will have no effect on servers that do support this control, but
085 * a criticality of {@code TRUE} will ensure that a server which does not
086 * support soft deletes does not attempt to process the add request.  If the
087 * criticality were {@code FALSE}, then any server that does not support the
088 * control would simply ignore it and attempt to add the entry specified in the
089 * add request (which will have details about the undelete to be processed).
090 * <BR><BR>
091 * The control may optionally have a value.  If a value is provided, then it
092 * must be the encoded representation of an empty ASN.1 sequence, like:
093 * <PRE>
094 *   UndeleteRequestValue ::= SEQUENCE {
095 *     ... }
096 * </PRE>
097 * In the future, the value sequence may allow one or more elements to customize
098 * the behavior of the undelete operation, but at present no such elements are
099 * defined.
100 * See the documentation for the {@link SoftDeleteRequestControl} class for an
101 * example demonstrating the use of this control.
102 *
103 * @see  HardDeleteRequestControl
104 * @see  SoftDeleteRequestControl
105 */
106@NotMutable()
107@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
108public final class UndeleteRequestControl
109       extends Control
110{
111  /**
112   * The OID (1.3.6.1.4.1.30221.2.5.23) for the undelete request control.
113   */
114  @NotNull public static final String UNDELETE_REQUEST_OID =
115       "1.3.6.1.4.1.30221.2.5.23";
116
117
118
119  /**
120   * The name of the optional attribute used to specify a set of changes to
121   * apply to the soft-deleted entry during the course of the undelete.
122   */
123  @NotNull public static final String ATTR_CHANGES = "ds-undelete-changes";
124
125
126
127  /**
128   * The name of the optional attribute used to indicate whether the
129   * newly-undeleted user account should be disabled and prevented from
130   * authenticating.
131   */
132  @NotNull public static final String ATTR_DISABLE_ACCOUNT =
133       "ds-undelete-disable-account";
134
135
136
137  /**
138   * The name of the optional attribute used to indicate whether the
139   * newly-undeleted user will be required to change his/her password
140   * immediately after authenticating and before being required to request any
141   * other operations.
142   */
143  @NotNull public static final String ATTR_MUST_CHANGE_PASSWORD =
144       "ds-undelete-must-change-password";
145
146
147
148  /**
149   * The name of the optional attribute used to specify the new password for use
150   * in the newly-undeleted entry.
151   */
152  @NotNull public static final String ATTR_NEW_PASSWORD =
153       "ds-undelete-new-password";
154
155
156
157  /**
158   * The name of the optional attribute used to specify the password currently
159   * contained in the soft-deleted entry, to be validated as part of the
160   * undelete process.
161   */
162  @NotNull public static final String ATTR_OLD_PASSWORD =
163       "ds-undelete-old-password";
164
165
166
167  /**
168   * The name of the required attribute used to specify the DN of the
169   * soft-deleted entry to be undeleted.
170   */
171  @NotNull public static final String ATTR_SOFT_DELETED_ENTRY_DN =
172       "ds-undelete-from-dn";
173
174
175
176  /**
177   * The serial version UID for this serializable class.
178   */
179  private static final long serialVersionUID = 5338045977962112876L;
180
181
182
183  /**
184   * Creates a undelete request control with a criticality of TRUE and no value.
185   */
186  public UndeleteRequestControl()
187  {
188    super(UNDELETE_REQUEST_OID, true, null);
189  }
190
191
192
193  /**
194   * Creates a new undelete request control which is decoded from the
195   * provided generic control.
196   *
197   * @param  control  The generic control to be decoded as an undelete request
198   *                  control.
199   *
200   * @throws  LDAPException  If the provided control cannot be decoded as an
201   *                         undelete request control.
202   */
203  public UndeleteRequestControl(@NotNull final Control control)
204         throws LDAPException
205  {
206    super(control);
207
208    if (control.hasValue())
209    {
210      try
211      {
212        final ASN1Sequence valueSequence =
213             ASN1Sequence.decodeAsSequence(control.getValue().getValue());
214        final ASN1Element[] elements = valueSequence.elements();
215        if (elements.length > 0)
216        {
217          throw new LDAPException(ResultCode.DECODING_ERROR,
218               ERR_UNDELETE_REQUEST_UNSUPPORTED_VALUE_ELEMENT_TYPE.get(
219                    StaticUtils.toHex(elements[0].getType())));
220        }
221      }
222      catch (final LDAPException le)
223      {
224        Debug.debugException(le);
225        throw le;
226      }
227      catch (final Exception e)
228      {
229        Debug.debugException(e);
230        throw new LDAPException(ResultCode.DECODING_ERROR,
231             ERR_UNDELETE_REQUEST_CANNOT_DECODE_VALUE.get(
232                  StaticUtils.getExceptionMessage(e)),
233             e);
234      }
235    }
236  }
237
238
239
240  /**
241   * Creates a new undelete request that may be used to recover the specified
242   * soft-deleted entry.
243   *
244   * @param  targetDN            The DN to use for the entry recovered
245   *                             from the soft-deleted entry contents.  It must
246   *                             not be {@code null}.
247   * @param  softDeletedEntryDN  The DN of the soft-deleted entry to be used in
248   *                             the restore process.  It must not be
249   *                             {@code null}.
250   *
251   * @return  An add request with an appropriate set of content
252   */
253  @NotNull()
254  public static AddRequest createUndeleteRequest(@NotNull final String targetDN,
255                                @NotNull final String softDeletedEntryDN)
256  {
257    return createUndeleteRequest(targetDN, softDeletedEntryDN, null, null, null,
258         null, null);
259  }
260
261
262
263  /**
264   * Creates a new undelete request that may be used to recover the specified
265   * soft-deleted entry.
266   *
267   * @param  targetDN            The DN to use for the entry recovered
268   *                             from the soft-deleted entry contents.  It must
269   *                             not be {@code null}.
270   * @param  softDeletedEntryDN  The DN of the soft-deleted entry to be used in
271   *                             the restore process.  It must not be
272   *                             {@code null}.
273   * @param  changes             An optional set of changes that should be
274   *                             applied to the entry during the course of
275   *                             undelete processing.  It may be {@code null} or
276   *                             empty if this element should be omitted from
277   *                             the resulting add request.
278   * @param  oldPassword         An optional copy of the password currently
279   *                             contained in the soft-deleted entry to be
280   *                             recovered.  If this is non-{@code null}, then
281   *                             this password will be required to match that
282   *                             contained in the target entry for the undelete
283   *                             to succeed.
284   * @param  newPassword         An optional new password to set for the user
285   *                             as part of the undelete processing.  It may be
286   *                             {@code null} if no new password should be
287   *                             provided.
288   * @param  mustChangePassword  Indicates whether the recovered user will be
289   *                             required to change his/her password before
290   *                             being allowed to request any other operations.
291   *                             It may be {@code null} if this should be
292   *                             omitted from the resulting add request.
293   * @param  disableAccount      Indicates whether the undeleted entry should be
294   *                             made disabled so that it cannot be used to
295   *                             authenticate.  It may be {@code null} if this
296   *                             should be omitted from the resulting add
297   *                             request.
298   *
299   * @return  An add request with an appropriate set of content
300   */
301  @NotNull()
302  public static AddRequest createUndeleteRequest(@NotNull final String targetDN,
303                                @NotNull final String softDeletedEntryDN,
304                                @Nullable final List<Modification> changes,
305                                @Nullable final String oldPassword,
306                                @Nullable final String newPassword,
307                                @Nullable final Boolean mustChangePassword,
308                                @Nullable final Boolean disableAccount)
309  {
310    final ArrayList<Attribute> attributes = new ArrayList<>(6);
311    attributes.add(new Attribute(ATTR_SOFT_DELETED_ENTRY_DN,
312         softDeletedEntryDN));
313
314    if ((changes != null) && (! changes.isEmpty()))
315    {
316      // The changes attribute should be an LDIF-encoded representation of the
317      // modification, with the first two lines (the DN and changetype)
318      // removed.
319      final LDIFModifyChangeRecord changeRecord =
320           new LDIFModifyChangeRecord(targetDN, changes);
321      final String[] modLdifLines = changeRecord.toLDIF(0);
322      final StringBuilder modLDIFBuffer = new StringBuilder();
323      for (int i=2; i < modLdifLines.length; i++)
324      {
325        modLDIFBuffer.append(modLdifLines[i]);
326        modLDIFBuffer.append(StaticUtils.EOL);
327      }
328      attributes.add(new Attribute(ATTR_CHANGES,
329           OctetStringMatchingRule.getInstance(), modLDIFBuffer.toString()));
330    }
331
332    if (oldPassword != null)
333    {
334      attributes.add(new Attribute(ATTR_OLD_PASSWORD,
335           OctetStringMatchingRule.getInstance(), oldPassword));
336    }
337
338    if (newPassword != null)
339    {
340      attributes.add(new Attribute(ATTR_NEW_PASSWORD,
341           OctetStringMatchingRule.getInstance(), newPassword));
342    }
343
344    if (mustChangePassword != null)
345    {
346      attributes.add(new Attribute(ATTR_MUST_CHANGE_PASSWORD,
347           BooleanMatchingRule.getInstance(),
348           (mustChangePassword ? "true" : "false")));
349    }
350
351    if (disableAccount != null)
352    {
353      attributes.add(new Attribute(ATTR_DISABLE_ACCOUNT,
354           BooleanMatchingRule.getInstance(),
355           (disableAccount ? "true" : "false")));
356    }
357
358    final Control[] controls =
359    {
360      new UndeleteRequestControl()
361    };
362
363    return new AddRequest(targetDN, attributes, controls);
364  }
365
366
367
368  /**
369   * {@inheritDoc}
370   */
371  @Override()
372  @NotNull()
373  public String getControlName()
374  {
375    return INFO_CONTROL_NAME_UNDELETE_REQUEST.get();
376  }
377
378
379
380  /**
381   * {@inheritDoc}
382   */
383  @Override()
384  public void toString(@NotNull final StringBuilder buffer)
385  {
386    buffer.append("UndeleteRequestControl(isCritical=");
387    buffer.append(isCritical());
388    buffer.append(')');
389  }
390}