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