001/*
002 * Copyright 2008-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2008-2024 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) 2008-2024 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.controls;
037
038
039
040import java.util.Collections;
041import java.util.EnumSet;
042import java.util.HashMap;
043import java.util.HashSet;
044import java.util.List;
045import java.util.Map;
046import java.util.Set;
047import java.util.StringTokenizer;
048import java.util.logging.Level;
049
050import com.unboundid.ldap.sdk.Attribute;
051import com.unboundid.ldap.sdk.Entry;
052import com.unboundid.ldap.sdk.ReadOnlyEntry;
053import com.unboundid.util.Debug;
054import com.unboundid.util.DebugType;
055import com.unboundid.util.NotMutable;
056import com.unboundid.util.NotNull;
057import com.unboundid.util.Nullable;
058import com.unboundid.util.StaticUtils;
059import com.unboundid.util.ThreadSafety;
060import com.unboundid.util.ThreadSafetyLevel;
061import com.unboundid.util.Validator;
062
063
064
065/**
066 * This class provides a mechanism for extracting the effective rights
067 * information from an entry returned for a search request that included the
068 * get effective rights request control.  In particular, it provides the ability
069 * to parse the values of the aclRights attributes in order to determine what
070 * rights the specified user may have when interacting with the entry.
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 * See the {@link GetEffectiveRightsRequestControl} for an example that
083 * demonstrates the use of the get effective rights request control and this
084 * entry.
085 */
086@NotMutable()
087@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
088public final class EffectiveRightsEntry
089       extends ReadOnlyEntry
090{
091  /**
092   * The name of the attribute that includes the rights information.
093   */
094  @NotNull private static final String ATTR_ACL_RIGHTS = "aclRights";
095
096
097
098  /**
099   * The serial version UID for this serializable class.
100   */
101  private static final long serialVersionUID = -3203127456449579174L;
102
103
104
105  // The set of entry-level rights parsed from the entry.
106  @Nullable private final Set<EntryRight> entryRights;
107
108  // The set of attribute-level rights parsed from the entry, mapped from the
109  // name of the attribute to the set of the corresponding attribute rights.
110  @Nullable private final Map<String,Set<AttributeRight>> attributeRights;
111
112
113
114  /**
115   * Creates a new get effective rights entry from the provided entry.
116   *
117   * @param  entry  The entry to use to create this get effective rights entry.
118   *                It must not be {@code null}.
119   */
120  public EffectiveRightsEntry(@NotNull final Entry entry)
121  {
122    super(entry);
123
124    final HashSet<String> options = StaticUtils.hashSetOf("entryLevel");
125    List<Attribute> attrList =
126         getAttributesWithOptions(ATTR_ACL_RIGHTS, options);
127    if ((attrList == null) || attrList.isEmpty())
128    {
129      if (Debug.debugEnabled(DebugType.LDAP))
130      {
131        Debug.debug(Level.WARNING, DebugType.LDAP,
132             "No entry-level aclRights information contained in entry " +
133                  entry.getDN());
134      }
135
136      entryRights = null;
137    }
138    else
139    {
140      entryRights = Collections.unmodifiableSet(parseEntryRights(attrList));
141    }
142
143    options.clear();
144    options.add("attributeLevel");
145    attrList = getAttributesWithOptions(ATTR_ACL_RIGHTS, options);
146    if ((attrList == null) || attrList.isEmpty())
147    {
148      if (Debug.debugEnabled(DebugType.LDAP))
149      {
150        Debug.debug(Level.WARNING, DebugType.LDAP,
151             "No attribute-level aclRights information contained in entry " +
152                  entry.getDN());
153      }
154
155      attributeRights = null;
156    }
157    else
158    {
159      final HashMap<String,Set<AttributeRight>> attrRightsMap =
160           new HashMap<>(StaticUtils.computeMapCapacity(attrList.size()));
161      for (final Attribute a : attrList)
162      {
163        final Set<String> attrOptions = a.getOptions();
164        String attrName = null;
165        for (final String s : attrOptions)
166        {
167          if (! s.equalsIgnoreCase("attributeLevel"))
168          {
169            attrName = s;
170          }
171        }
172
173        if (attrName == null)
174        {
175          if (Debug.debugEnabled(DebugType.LDAP))
176          {
177            Debug.debug(Level.WARNING, DebugType.LDAP,
178                 "Unable to determine the target attribute name from " +
179                      a.getName());
180          }
181        }
182        else
183        {
184          final String lowerName = StaticUtils.toLowerCase(attrName);
185          final Set<AttributeRight> rights = parseAttributeRights(a);
186          attrRightsMap.put(lowerName, rights);
187        }
188      }
189
190      attributeRights = Collections.unmodifiableMap(attrRightsMap);
191    }
192  }
193
194
195
196  /**
197   * Parses the entry rights information from the entry.
198   *
199   * @param  attrList  The list of attributes to be parsed.
200   *
201   * @return  The set of entry rights parsed from the entry.
202   */
203  @NotNull()
204  private static Set<EntryRight> parseEntryRights(
205                                      @NotNull final List<Attribute> attrList)
206  {
207    final EnumSet<EntryRight> entryRightsSet = EnumSet.noneOf(EntryRight.class);
208    for (final Attribute a : attrList)
209    {
210      for (final String value : a.getValues())
211      {
212        final StringTokenizer tokenizer = new StringTokenizer(value, ", ");
213        while (tokenizer.hasMoreTokens())
214        {
215          final String token = tokenizer.nextToken();
216          if (token.endsWith(":1"))
217          {
218            final String rightName = token.substring(0, token.length()-2);
219            final EntryRight r = EntryRight.forName(rightName);
220            if (r == null)
221            {
222              if (Debug.debugEnabled(DebugType.LDAP))
223              {
224                Debug.debug(Level.WARNING, DebugType.LDAP,
225                     "Unrecognized entry right " + rightName);
226              }
227            }
228            else
229            {
230              entryRightsSet.add(r);
231            }
232          }
233        }
234      }
235    }
236
237    return entryRightsSet;
238  }
239
240
241
242  /**
243   * Parses the attribute rights information from the provided attribute.
244   *
245   * @param  a  The attribute to be parsed.
246   *
247   * @return  The set of attribute rights parsed from the provided attribute.
248   */
249  @NotNull()
250  private static Set<AttributeRight> parseAttributeRights(
251                                          @NotNull final Attribute a)
252  {
253    final EnumSet<AttributeRight> rightsSet =
254         EnumSet.noneOf(AttributeRight.class);
255
256    for (final String value : a.getValues())
257    {
258      final StringTokenizer tokenizer = new StringTokenizer(value, ", ");
259      while (tokenizer.hasMoreTokens())
260      {
261        final String token = tokenizer.nextToken();
262        if (token.endsWith(":1"))
263        {
264          final String rightName = token.substring(0, token.length()-2);
265          final AttributeRight r = AttributeRight.forName(rightName);
266          if (r == null)
267          {
268            if (Debug.debugEnabled(DebugType.LDAP))
269            {
270              Debug.debug(Level.WARNING, DebugType.LDAP,
271                   "Unrecognized attribute right " + rightName);
272            }
273          }
274          else
275          {
276            rightsSet.add(r);
277          }
278        }
279      }
280    }
281
282    return rightsSet;
283  }
284
285
286
287  /**
288   * Indicates whether any access control rights information was contained in
289   * the entry.
290   *
291   * @return  {@code true} if access control rights information was contained in
292   *          the entry, or {@code false} if not.
293   */
294  public boolean rightsInformationAvailable()
295  {
296    return ((entryRights != null) || (attributeRights != null));
297  }
298
299
300
301  /**
302   * Retrieves the set of entry-level rights parsed from the entry.
303   *
304   * @return  The set of entry-level rights parsed from the entry, or
305   *          {@code null} if the entry did not have any entry-level rights
306   *          information.
307   */
308  @Nullable()
309  public Set<EntryRight> getEntryRights()
310  {
311    return entryRights;
312  }
313
314
315
316  /**
317   * Indicates whether the specified entry right is granted for this entry.
318   *
319   * @param  entryRight  The entry right for which to make the determination.
320   *                     It must not be {@code null}.
321   *
322   * @return  {@code true} if the entry included entry-level rights information
323   *          and the specified entry right is granted, or {@code false} if not.
324   */
325  public boolean hasEntryRight(@NotNull final EntryRight entryRight)
326  {
327    Validator.ensureNotNull(entryRight);
328
329    return ((entryRights != null) && entryRights.contains(entryRight));
330  }
331
332
333
334  /**
335   * Retrieves the set of attribute-level rights parsed from the entry, mapped
336   * from attribute name (in all lowercase characters) to the set of
337   * attribute-level rights for that attribute.
338   *
339   * @return  The set of attribute-level rights parsed from the entry, or
340   *          {@code null} if the entry did not have any attribute-level rights
341   *          information.
342   */
343  @Nullable()
344  public Map<String,Set<AttributeRight>> getAttributeRights()
345  {
346    return attributeRights;
347  }
348
349
350
351  /**
352   * Retrieves the set of attribute-level rights parsed from the entry for the
353   * specified attribute.
354   *
355   * @param  attributeName  The name of the attribute for which to retrieve the
356   *                        attribute-level rights.  It must not be
357   *                        {@code null}.
358   *
359   * @return  The set of attribute-level rights for the specified attribute, or
360   *          {@code null} if the entry did not include any attribute-level
361   *          rights information for the specified attribute.
362   */
363  @Nullable()
364  public Set<AttributeRight> getAttributeRights(
365                                  @NotNull final String attributeName)
366  {
367    Validator.ensureNotNull(attributeName);
368
369    if (attributeRights == null)
370    {
371      return null;
372    }
373
374    return attributeRights.get(StaticUtils.toLowerCase(attributeName));
375  }
376
377
378
379  /**
380   * Indicates whether the specified attribute right is granted for the
381   * specified attribute in this entry.
382   *
383   * @param  attributeRight  The attribute right for which to make the
384   *                         determination.  It must not be {@code null}.
385   * @param  attributeName   The name of the attribute for which to make the
386   *                         determination.  It must not be {@code null}.
387   *
388   * @return  {@code true} if the entry included attribute-level rights
389   *          information for the specified attribute and the indicated right is
390   *          granted, or {@code false} if not.
391   */
392  public boolean hasAttributeRight(@NotNull final AttributeRight attributeRight,
393                                   @NotNull final String attributeName)
394  {
395    Validator.ensureNotNull(attributeName, attributeRight);
396
397    final Set<AttributeRight> attrRights = getAttributeRights(attributeName);
398    return ((attrRights != null) && attrRights.contains(attributeRight));
399  }
400}