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