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