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.extensions;
022
023
024
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.Collection;
028import java.util.Collections;
029import java.util.Iterator;
030import java.util.List;
031
032import com.unboundid.asn1.ASN1Element;
033import com.unboundid.asn1.ASN1Enumerated;
034import com.unboundid.asn1.ASN1OctetString;
035import com.unboundid.asn1.ASN1Sequence;
036import com.unboundid.ldap.sdk.Control;
037import com.unboundid.ldap.sdk.ExtendedRequest;
038import com.unboundid.ldap.sdk.LDAPException;
039import com.unboundid.ldap.sdk.ResultCode;
040import com.unboundid.util.Debug;
041import com.unboundid.util.NotMutable;
042import com.unboundid.util.StaticUtils;
043import com.unboundid.util.ThreadSafety;
044import com.unboundid.util.ThreadSafetyLevel;
045import com.unboundid.util.Validator;
046
047import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*;
048
049
050
051/**
052 * This class provides an implementation of an extended request that may be used
053 * to set the accessibility of one or more subtrees in the Ping Identity,
054 * UnboundID, or Alcatel-Lucent 8661 Directory Server.  It may be used to
055 * indicate that a specified set of entries and all their subordinates should be
056 * invisible or read-only, or to restore it to full accessibility.
057 * <BR>
058 * <BLOCKQUOTE>
059 *   <B>NOTE:</B>  This class, and other classes within the
060 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
061 *   supported for use against Ping Identity, UnboundID, and Alcatel-Lucent 8661
062 *   server products.  These classes provide support for proprietary
063 *   functionality or for external specifications that are not considered stable
064 *   or mature enough to be guaranteed to work in an interoperable way with
065 *   other types of LDAP servers.
066 * </BLOCKQUOTE>
067 * <BR>
068 * The OID for this request is 1.3.6.1.4.1.30221.2.6.19, and the
069 * value must have the encoding specified below.  Note that the initial
070 * specification for this extended request only allowed for the specification of
071 * a single subtree, whereas it is now possible to affect the accessibility of
072 * multiple subtrees in a single request.  In order to preserve compatibility
073 * with the original encoding, if there is more than one target subtree, then
074 * the first subtree must be specified as the first element in the value
075 * sequence and the remaining subtrees must be specified in the
076 * additionalSubtreeBaseDNs element.
077 * <BR><BR>
078 * <PRE>
079 *   SetSubtreeAccessibilityRequestValue ::= SEQUENCE {
080 *        subtreeBaseDN                LDAPDN,
081 *        subtreeAccessibility         ENUMERATED {
082 *             accessible                 (0),
083 *             read-only-bind-allowed     (1),
084 *             read-only-bind-denied      (2),
085 *             hidden                     (3),
086 *             ... },
087 *        bypassUserDN                 [0] LDAPDN OPTIONAL,
088 *        additionalSubtreeBaseDNs     [1] SEQUENCE OF LDAPDN OPTIONAL,
089 *        ... }
090 * </PRE>
091 */
092@NotMutable()
093@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
094public final class SetSubtreeAccessibilityExtendedRequest
095       extends ExtendedRequest
096{
097  /**
098   * The OID (1.3.6.1.4.1.30221.2.6.19) for the set subtree accessibility
099   * extended request.
100   */
101  public static final String SET_SUBTREE_ACCESSIBILITY_REQUEST_OID =
102       "1.3.6.1.4.1.30221.2.6.19";
103
104
105
106  /**
107   * The BER type for the bypass user DN element of the request.
108   */
109  private static final byte TYPE_BYPASS_USER_DN = (byte) 0x80;
110
111
112
113  /**
114   * The BER type for the set of additional subtree base DNs.
115   */
116  private static final byte TYPE_ADDITIONAL_SUBTREE_BASE_DNS = (byte) 0xA1;
117
118
119
120  /**
121   * The serial version UID for this serializable class.
122   */
123  private static final long serialVersionUID = -3003738735546060245L;
124
125
126
127  // The set of subtree base DNs included in the request.
128  private final List<String> subtreeBaseDNs;
129
130  // The DN of a user who will be exempted from the restrictions.  This is not
131  // applicable for a subtree accessibility of ACCESSIBLE.
132  private final String bypassUserDN;
133
134  // The accessibility state to use for the target subtrees.
135  private final SubtreeAccessibilityState accessibilityState;
136
137
138
139  /**
140   * Creates a new set subtree accessibility extended request with the provided
141   * information.
142   *
143   * @param  subtreeBaseDNs      The set of base DNs for the target subtree.
144   *                             It must not be {@code null} or empty.
145   * @param  accessibilityState  The accessibility state to use for the target
146   *                             subtrees.
147   * @param  bypassUserDN        The DN of a user that will be allowed to bypass
148   *                             restrictions on the target subtrees.
149   * @param  controls            The set of controls to include in the request.
150   */
151  private SetSubtreeAccessibilityExtendedRequest(
152               final Collection<String> subtreeBaseDNs,
153               final SubtreeAccessibilityState accessibilityState,
154               final String bypassUserDN,
155               final Control... controls)
156  {
157    super(SET_SUBTREE_ACCESSIBILITY_REQUEST_OID,
158         encodeValue(subtreeBaseDNs, accessibilityState, bypassUserDN),
159         controls);
160
161    this.subtreeBaseDNs     = Collections.unmodifiableList(
162         new ArrayList<String>(subtreeBaseDNs));
163    this.accessibilityState = accessibilityState;
164    this.bypassUserDN       = bypassUserDN;
165  }
166
167
168
169  /**
170   * Encodes the provided information for use as the extended request value.
171   *
172   * @param  subtreeBaseDNs      The set of base DNs for the target subtrees.
173   *                             It must not be {@code null} or empty.
174   * @param  accessibilityState  The accessibility state to use for the target
175   *                             subtrees.
176   * @param  bypassUserDN        The DN of a user that will be allowed to bypass
177   *                             restrictions on the target subtrees.
178   *
179   * @return  An ASN.1 octet string containing the encoded value.
180   */
181  private static ASN1OctetString encodeValue(
182                      final Collection<String> subtreeBaseDNs,
183                      final SubtreeAccessibilityState accessibilityState,
184                      final String bypassUserDN)
185  {
186    final Iterator<String> dnIterator = subtreeBaseDNs.iterator();
187    final String subtreeBaseDN = dnIterator.next();
188    Validator.ensureNotNull(subtreeBaseDN);
189
190    final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(4);
191    elements.add(new ASN1OctetString(subtreeBaseDN));
192    elements.add(new ASN1Enumerated(accessibilityState.intValue()));
193
194    if (bypassUserDN != null)
195    {
196      elements.add(new ASN1OctetString(TYPE_BYPASS_USER_DN, bypassUserDN));
197    }
198
199    if (dnIterator.hasNext())
200    {
201      final ArrayList<ASN1Element> additionalDNElements =
202           new ArrayList<ASN1Element>(subtreeBaseDNs.size()-1);
203      while (dnIterator.hasNext())
204      {
205        final String additionalDN = dnIterator.next();
206        Validator.ensureNotNull(additionalDN);
207        additionalDNElements.add(new ASN1OctetString(additionalDN));
208      }
209      elements.add(new ASN1Sequence(TYPE_ADDITIONAL_SUBTREE_BASE_DNS,
210           additionalDNElements));
211    }
212
213    return new ASN1OctetString(new ASN1Sequence(elements).encode());
214  }
215
216
217
218  /**
219   * Creates a new set subtree accessibility extended request from the provided
220   * generic extended request.
221   *
222   * @param  extendedRequest  The generic extended request to use to create this
223   *                          set subtree accessibility extended request.
224   *
225   * @throws  LDAPException  If a problem occurs while decoding the request.
226   */
227  public SetSubtreeAccessibilityExtendedRequest(
228              final ExtendedRequest extendedRequest)
229         throws LDAPException
230  {
231    super(extendedRequest);
232
233    final ASN1OctetString value = extendedRequest.getValue();
234    if (value == null)
235    {
236      throw new LDAPException(ResultCode.DECODING_ERROR,
237           ERR_SET_SUBTREE_ACCESSIBILITY_NO_VALUE.get());
238    }
239
240    try
241    {
242      final ASN1Element[] elements =
243           ASN1Sequence.decodeAsSequence(value.getValue()).elements();
244
245      final List<String> baseDNs = new ArrayList<String>(10);
246      baseDNs.add(ASN1OctetString.decodeAsOctetString(
247           elements[0]).stringValue());
248
249      final int accessibilityStateValue =
250           ASN1Enumerated.decodeAsEnumerated(elements[1]).intValue();
251      accessibilityState =
252           SubtreeAccessibilityState.valueOf(accessibilityStateValue);
253      if (accessibilityState == null)
254      {
255        throw new LDAPException(ResultCode.DECODING_ERROR,
256             ERR_SET_SUBTREE_ACCESSIBILITY_INVALID_ACCESSIBILITY_STATE.get(
257                  accessibilityStateValue));
258      }
259
260      String bypassDN = null;
261      for (int i=2; i < elements.length; i++)
262      {
263        switch (elements[i].getType())
264        {
265          case TYPE_BYPASS_USER_DN:
266            bypassDN =
267                 ASN1OctetString.decodeAsOctetString(elements[i]).stringValue();
268            break;
269
270          case TYPE_ADDITIONAL_SUBTREE_BASE_DNS:
271            for (final ASN1Element e :
272                 ASN1Sequence.decodeAsSequence(elements[i]).elements())
273            {
274              baseDNs.add(ASN1OctetString.decodeAsOctetString(e).stringValue());
275            }
276            break;
277
278          default:
279            throw new LDAPException(ResultCode.DECODING_ERROR,
280                 ERR_SET_SUBTREE_ACCESSIBILITY_INVALID_ELEMENT_TYPE.get(
281                      StaticUtils.toHex(elements[i].getType())));
282        }
283      }
284      bypassUserDN = bypassDN;
285      subtreeBaseDNs = Collections.unmodifiableList(baseDNs);
286    }
287    catch (final LDAPException le)
288    {
289      Debug.debugException(le);
290      throw le;
291    }
292    catch (final Exception e)
293    {
294      Debug.debugException(e);
295      throw new LDAPException(ResultCode.DECODING_ERROR,
296           ERR_SET_SUBTREE_ACCESSIBILITY_CANNOT_DECODE.get(
297                StaticUtils.getExceptionMessage(e)),
298           e);
299    }
300
301
302    if ((accessibilityState == SubtreeAccessibilityState.ACCESSIBLE) &&
303        (bypassUserDN != null))
304    {
305      throw new LDAPException(ResultCode.DECODING_ERROR,
306           ERR_SET_SUBTREE_ACCESSIBILITY_UNEXPECTED_BYPASS_DN.get(
307                accessibilityState.getStateName()));
308    }
309  }
310
311
312
313  /**
314   * Creates a new set subtree accessibility extended request that will make the
315   * specified subtree accessible.
316   *
317   * @param  subtreeBaseDN  The base DN for the subtree to make accessible.  It
318   *                        must not be {@code null}.
319   * @param  controls       The set of controls to include in the request.  It
320   *                        may be {@code null} or empty if no controls are
321   *                        needed.
322   *
323   * @return  The set subtree accessibility extended request that was created.
324   */
325  public static SetSubtreeAccessibilityExtendedRequest
326                     createSetAccessibleRequest(final String subtreeBaseDN,
327                                                final Control... controls)
328  {
329    Validator.ensureNotNull(subtreeBaseDN);
330
331    return new SetSubtreeAccessibilityExtendedRequest(
332         Arrays.asList(subtreeBaseDN), SubtreeAccessibilityState.ACCESSIBLE,
333         null, controls);
334  }
335
336
337
338  /**
339   * Creates a new set subtree accessibility extended request that will make the
340   * specified subtrees accessible.
341   *
342   * @param  subtreeBaseDNs  The base DNs for the subtrees to make accessible.
343   *                         It must not be {@code null} or empty.  If multiple
344   *                         base DNs are specified, then all must reside below
345   *                         the same backend base DN.
346   * @param  controls        The set of controls to include in the request.  It
347   *                         may be {@code null} or empty if no controls are
348   *                         needed.
349   *
350   * @return  The set subtree accessibility extended request that was created.
351   */
352  public static SetSubtreeAccessibilityExtendedRequest
353                     createSetAccessibleRequest(
354                          final Collection<String> subtreeBaseDNs,
355                          final Control... controls)
356  {
357    Validator.ensureNotNull(subtreeBaseDNs);
358    Validator.ensureFalse(subtreeBaseDNs.isEmpty());
359
360    return new SetSubtreeAccessibilityExtendedRequest(subtreeBaseDNs,
361         SubtreeAccessibilityState.ACCESSIBLE, null, controls);
362  }
363
364
365
366  /**
367   * Creates a new set subtree accessibility extended request that will make the
368   * specified subtree read-only.
369   *
370   * @param  subtreeBaseDN  The base DN for the subtree to make read-only.  It
371   *                        must not be {@code null}.
372   * @param  allowBind      Indicates whether users within the specified subtree
373   *                        will be allowed to bind.
374   * @param  bypassUserDN   The DN of a user that will be allowed to perform
375   *                        write (add, delete, modify, and modify DN)
376   *                        operations in the specified subtree.  It may be
377   *                        {@code null} if no bypass user is needed.
378   * @param  controls       The set of controls to include in the request.  It
379   *                        may be {@code null} or empty if no controls are
380   *                        needed.
381   *
382   * @return  The set subtree accessibility extended request that was created.
383   */
384  public static SetSubtreeAccessibilityExtendedRequest
385              createSetReadOnlyRequest(final String subtreeBaseDN,
386                                       final boolean allowBind,
387                                       final String bypassUserDN,
388                                       final Control... controls)
389  {
390    Validator.ensureNotNull(subtreeBaseDN);
391
392    if (allowBind)
393    {
394      return new SetSubtreeAccessibilityExtendedRequest(
395           Arrays.asList(subtreeBaseDN),
396           SubtreeAccessibilityState.READ_ONLY_BIND_ALLOWED, bypassUserDN,
397           controls);
398    }
399    else
400    {
401      return new SetSubtreeAccessibilityExtendedRequest(
402           Arrays.asList(subtreeBaseDN),
403           SubtreeAccessibilityState.READ_ONLY_BIND_DENIED, bypassUserDN,
404           controls);
405    }
406  }
407
408
409
410  /**
411   * Creates a new set subtree accessibility extended request that will make the
412   * specified subtrees read-only.
413   *
414   * @param  subtreeBaseDNs  The base DNs for the subtrees to make read-only.
415   *                         It must not be {@code null} or empty.  If multiple
416   *                         base DNs are specified, then all must reside below
417   *                         the same backend base DN.
418   * @param  allowBind       Indicates whether users within the specified
419   *                         subtrees will be allowed to bind.
420   * @param  bypassUserDN    The DN of a user that will be allowed to perform
421   *                         write (add, delete, modify, and modify DN)
422   *                         operations in the specified subtrees.  It may be
423   *                         {@code null} if no bypass user is needed.
424   * @param  controls        The set of controls to include in the request.  It
425   *                         may be {@code null} or empty if no controls are
426   *                         needed.
427   *
428   * @return  The set subtree accessibility extended request that was created.
429   */
430  public static SetSubtreeAccessibilityExtendedRequest
431              createSetReadOnlyRequest(final Collection<String> subtreeBaseDNs,
432                                       final boolean allowBind,
433                                       final String bypassUserDN,
434                                       final Control... controls)
435  {
436    Validator.ensureNotNull(subtreeBaseDNs);
437    Validator.ensureFalse(subtreeBaseDNs.isEmpty());
438
439    if (allowBind)
440    {
441      return new SetSubtreeAccessibilityExtendedRequest(subtreeBaseDNs,
442           SubtreeAccessibilityState.READ_ONLY_BIND_ALLOWED, bypassUserDN,
443           controls);
444    }
445    else
446    {
447      return new SetSubtreeAccessibilityExtendedRequest(subtreeBaseDNs,
448           SubtreeAccessibilityState.READ_ONLY_BIND_DENIED, bypassUserDN,
449           controls);
450    }
451  }
452
453
454
455  /**
456   * Creates a new set subtree accessibility extended request that will make the
457   * specified subtree hidden.
458   *
459   * @param  subtreeBaseDN  The base DN for the subtree to make hidden.  It must
460   *                        not be {@code null}.
461   * @param  bypassUserDN   The DN of a user that will be allowed to perform
462   *                        write (add, delete, modify, and modify DN)
463   *                        operations in the specified subtree.  It may be
464   *                        {@code null} if no bypass user is needed.
465   * @param  controls       The set of controls to include in the request.  It
466   *                        may be {@code null} or empty if no controls are
467   *                        needed.
468   *
469   * @return  The set subtree accessibility extended request that was created.
470   */
471  public static SetSubtreeAccessibilityExtendedRequest
472              createSetHiddenRequest(final String subtreeBaseDN,
473                                     final String bypassUserDN,
474                                     final Control... controls)
475  {
476    Validator.ensureNotNull(subtreeBaseDN);
477
478    return new SetSubtreeAccessibilityExtendedRequest(
479         Arrays.asList(subtreeBaseDN), SubtreeAccessibilityState.HIDDEN,
480         bypassUserDN, controls);
481  }
482
483
484
485  /**
486   * Creates a new set subtree accessibility extended request that will make the
487   * specified subtrees hidden.
488   *
489   * @param  subtreeBaseDNs  The base DNs for the subtrees to make hidden.  It
490   *                         must not be {@code null} or empty.  If multiple
491   *                         base DNs are specified, then all must reside below
492   *                         the same backend base DN.
493   * @param  bypassUserDN    The DN of a user that will be allowed to perform
494   *                         write (add, delete, modify, and modify DN)
495   *                         operations in the specified subtrees.  It may be
496   *                         {@code null} if no bypass user is needed.
497   * @param  controls        The set of controls to include in the request.  It
498   *                         may be {@code null} or empty if no controls are
499   *                         needed.
500   *
501   * @return  The set subtree accessibility extended request that was created.
502   */
503  public static SetSubtreeAccessibilityExtendedRequest
504              createSetHiddenRequest(final Collection<String> subtreeBaseDNs,
505                                     final String bypassUserDN,
506                                     final Control... controls)
507  {
508    Validator.ensureNotNull(subtreeBaseDNs);
509    Validator.ensureFalse(subtreeBaseDNs.isEmpty());
510
511    return new SetSubtreeAccessibilityExtendedRequest(subtreeBaseDNs,
512         SubtreeAccessibilityState.HIDDEN, bypassUserDN, controls);
513  }
514
515
516
517  /**
518   * Retrieves the base DN for the target subtree.  Note that if multiple
519   * base DNs are defined, this will only retrieve the first.  The
520   * {@link #getSubtreeBaseDNs()} method should be used to get the complete set
521   * of target subtree base DNs.
522   *
523   * @return  The base DN for the target subtree.
524   */
525  public String getSubtreeBaseDN()
526  {
527    return subtreeBaseDNs.get(0);
528  }
529
530
531
532  /**
533   * Retrieves the base DNs for all target subtrees.
534   *
535   * @return  The base DNs for all target subtrees.
536   */
537  public List<String> getSubtreeBaseDNs()
538  {
539    return subtreeBaseDNs;
540  }
541
542
543
544  /**
545   * Retrieves the accessibility state to apply to the target subtrees.
546   *
547   * @return  The accessibility state to apply to the target subtrees.
548   */
549  public SubtreeAccessibilityState getAccessibilityState()
550  {
551    return accessibilityState;
552  }
553
554
555
556  /**
557   * Retrieves the DN of the user that will be allowed to bypass the
558   * restrictions imposed on the target subtrees for all other users.
559   *
560   * @return  The DN of the user that will be allowed to bypass the restrictions
561   *          imposed on the target subtrees for all other users, or
562   *          {@code null} if there are no restrictions to be imposed on the
563   *          target subtrees or if no bypass user is defined for those
564   *          subtrees.
565   */
566  public String getBypassUserDN()
567  {
568    return bypassUserDN;
569  }
570
571
572
573  /**
574   * {@inheritDoc}
575   */
576  @Override()
577  public SetSubtreeAccessibilityExtendedRequest duplicate()
578  {
579    return duplicate(getControls());
580  }
581
582
583
584  /**
585   * {@inheritDoc}
586   */
587  @Override()
588  public SetSubtreeAccessibilityExtendedRequest duplicate(
589              final Control[] controls)
590  {
591    return new SetSubtreeAccessibilityExtendedRequest(subtreeBaseDNs,
592         accessibilityState, bypassUserDN, controls);
593  }
594
595
596
597  /**
598   * {@inheritDoc}
599   */
600  @Override()
601  public String getExtendedRequestName()
602  {
603    return INFO_EXTENDED_REQUEST_NAME_SET_SUBTREE_ACCESSIBILITY.get();
604  }
605
606
607
608  /**
609   * {@inheritDoc}
610   */
611  @Override()
612  public void toString(final StringBuilder buffer)
613  {
614    buffer.append("SetSubtreeAccessibilityExtendedRequest(baseDNs={");
615
616    final Iterator<String> dnIterator = subtreeBaseDNs.iterator();
617    while (dnIterator.hasNext())
618    {
619      buffer.append('"');
620      buffer.append(dnIterator.next());
621      buffer.append('"');
622
623      if (dnIterator.hasNext())
624      {
625        buffer.append(", ");
626      }
627    }
628
629    buffer.append("}, accessibilityType=\"");
630    buffer.append(accessibilityState.getStateName());
631    buffer.append('"');
632
633    if (bypassUserDN != null)
634    {
635      buffer.append(", bypassUserDN=\"");
636      buffer.append(bypassUserDN);
637      buffer.append('"');
638    }
639
640    final Control[] controls = getControls();
641    if (controls.length > 0)
642    {
643      buffer.append(", controls={");
644      for (int i=0; i < controls.length; i++)
645      {
646        if (i > 0)
647        {
648          buffer.append(", ");
649        }
650
651        buffer.append(controls[i]);
652      }
653      buffer.append('}');
654    }
655
656    buffer.append(')');
657  }
658}