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