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