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.ArrayList;
041import java.util.LinkedHashMap;
042import java.util.List;
043import java.util.Map;
044
045import com.unboundid.asn1.ASN1Element;
046import com.unboundid.asn1.ASN1OctetString;
047import com.unboundid.asn1.ASN1Sequence;
048import com.unboundid.ldap.sdk.Control;
049import com.unboundid.ldap.sdk.JSONControlDecodeHelper;
050import com.unboundid.ldap.sdk.LDAPException;
051import com.unboundid.ldap.sdk.ResultCode;
052import com.unboundid.util.Debug;
053import com.unboundid.util.NotMutable;
054import com.unboundid.util.NotNull;
055import com.unboundid.util.Nullable;
056import com.unboundid.util.StaticUtils;
057import com.unboundid.util.ThreadSafety;
058import com.unboundid.util.ThreadSafetyLevel;
059import com.unboundid.util.Validator;
060import com.unboundid.util.json.JSONArray;
061import com.unboundid.util.json.JSONField;
062import com.unboundid.util.json.JSONObject;
063import com.unboundid.util.json.JSONString;
064import com.unboundid.util.json.JSONValue;
065
066import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
067
068
069
070/**
071 * This class provides an implementation of the get effective rights request
072 * control, which may be included in a search request to indicate that matching
073 * entries should include information about the rights a given user may have
074 * when interacting with that entry.
075 * <BR>
076 * <BLOCKQUOTE>
077 *   <B>NOTE:</B>  This class, and other classes within the
078 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
079 *   supported for use against Ping Identity, UnboundID, and
080 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
081 *   for proprietary functionality or for external specifications that are not
082 *   considered stable or mature enough to be guaranteed to work in an
083 *   interoperable way with other types of LDAP servers.
084 * </BLOCKQUOTE>
085 * <BR>
086 * When the get effective rights control is included in a search request, then
087 * each entry returned may include information about the rights that the
088 * specified user has for that entry in the {@code aclRights} operational
089 * attribute.  Note that because this is an operational attribute, it must be
090 * explicitly included in the set of attributes to return.
091 * <BR><BR>
092 * If the {@code aclRights} attribute is included in the entry, then it will be
093 * present with multiple sets of options.  In one case, it will have an option
094 * of "entryLevel", which provides information about the rights that the user
095 * has for the entry in general (see the {@link EntryRight} enum for a list of
096 * the entry-level rights that can be held).  In all other cases, it will have
097 * one option of "attributeLevel" and another option that is the name of the
098 * attribute for which the set of rights is granted (see the
099 * {@link AttributeRight} enum for a list of the attribute-level rights that can
100 * be held).  In either case, the value will be a comma-delimited list of
101 * right strings, where each right string is the name of the right followed by
102 * a colon and a one to indicate that the right is granted or zero to indicate
103 * that it is not granted.  The {@link EffectiveRightsEntry} class provides a
104 * simple means of accessing the information encoded in the values of the
105 * {@code aclRights} attribute.
106 * <BR><BR>
107 * This control was designed by Sun Microsystems, and it is not the same as the
108 * get effective rights control referenced in the draft-ietf-ldapext-acl-model
109 * Internet draft.  The value for this control should be encoded as follows:
110 * <BR><BR>
111 * <PRE>
112 * GET_EFFECTIVE_RIGHTS := SEQUENCE {
113 *   authzID     authzID,
114 *   attributes  SEQUENCE OF AttributeType OPTIONAL }
115 * </PRE>
116 * <H2>Example</H2>
117 * The following example demonstrates the use of the get effective rights
118 * control to determine whether user "uid=admin,dc=example,dc=com" has the
119 * ability to change the password for the user with uid "john.doe":
120 * <PRE>
121 * SearchRequest searchRequest = new SearchRequest("dc=example,dc=com",
122 *      SearchScope.SUB, Filter.createEqualityFilter("uid", "john.doe"),
123 *      "userPassword", "aclRights");
124 * searchRequest.addControl(new GetEffectiveRightsRequestControl(
125 *      "dn:uid=admin,dc=example,dc=com"));
126 * SearchResult searchResult = connection.search(searchRequest);
127 *
128 * for (SearchResultEntry entry : searchResult.getSearchEntries())
129 * {
130 *   EffectiveRightsEntry effectiveRightsEntry =
131 *        new EffectiveRightsEntry(entry);
132 *   if (effectiveRightsEntry.rightsInformationAvailable())
133 *   {
134 *     if (effectiveRightsEntry.hasAttributeRight(AttributeRight.WRITE,
135 *          "userPassword"))
136 *     {
137 *       // The admin user has permission to change the target user's password.
138 *     }
139 *     else
140 *     {
141 *       // The admin user does not have permission to change the target user's
142 *       // password.
143 *     }
144 *   }
145 *   else
146 *   {
147 *     // No effective rights information was returned.
148 *   }
149 * }
150 * </PRE>
151 */
152@NotMutable()
153@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
154public final class GetEffectiveRightsRequestControl
155       extends Control
156{
157  /**
158   * The OID (1.3.6.1.4.1.42.2.27.9.5.2) for the get effective rights request
159   * control.
160   */
161  @NotNull public static final String GET_EFFECTIVE_RIGHTS_REQUEST_OID =
162       "1.3.6.1.4.1.42.2.27.9.5.2";
163
164
165
166  /**
167   * The name of the field used to specify the set of target attributes in the
168   * JSON representation of this control.
169   */
170  @NotNull private static final String JSON_FIELD_ATTRIBUTES = "attributes";
171
172
173
174  /**
175   * The name of the field used to specify the authorization ID in the JSON
176   * representation of this control.
177   */
178  @NotNull private static final String JSON_FIELD_AUTHORIZATION_ID =
179       "authorization-id";
180
181
182
183  /**
184   * The serial version UID for this serializable class.
185   */
186  private static final long serialVersionUID = 354733122036206073L;
187
188
189
190  // The authorization ID of the user for which to calculate the effective
191  // rights.
192  @NotNull private final String authzID;
193
194  // The names of the attribute types for which to calculate the effective
195  // rights.
196  @NotNull private final String[] attributes;
197
198
199
200  /**
201   * Creates a new get effective rights request control with the provided
202   * information.  It will not be marked critical.
203   *
204   * @param  authzID     The authorization ID of the user for whom the effective
205   *                     rights should be calculated.  It must not be
206   *                     {@code null}.
207   * @param  attributes  The set of attributes for which to calculate the
208   *                     effective rights.
209   */
210  public GetEffectiveRightsRequestControl(@NotNull final String authzID,
211                                          @NotNull final String... attributes)
212  {
213    this(false, authzID, attributes);
214  }
215
216
217
218  /**
219   * Creates a new get effective rights request control with the provided
220   * information.  It will not be marked critical.
221   *
222   * @param  isCritical  Indicates whether this control should be marked
223   *                     critical.
224   * @param  authzID     The authorization ID of the user for whom the effective
225   *                     rights should be calculated.  It must not be
226   *                     {@code null}.
227   * @param  attributes  The set of attributes for which to calculate the
228   *                     effective rights.
229   */
230  public GetEffectiveRightsRequestControl(final boolean isCritical,
231                                          @NotNull final String authzID,
232                                          @NotNull final String... attributes)
233  {
234    super(GET_EFFECTIVE_RIGHTS_REQUEST_OID, isCritical,
235          encodeValue(authzID, attributes));
236
237    this.authzID    = authzID;
238    this.attributes = attributes;
239  }
240
241
242
243  /**
244   * Creates a new get effective rights request control which is decoded from
245   * the provided generic control.
246   *
247   * @param  control  The generic control to be decoded as a get effective
248   *                  rights request control.
249   *
250   * @throws  LDAPException  If the provided control cannot be decoded as a get
251   *                         effective rights request control.
252   */
253  public GetEffectiveRightsRequestControl(@NotNull final Control control)
254         throws LDAPException
255  {
256    super(control);
257
258    final ASN1OctetString value = control.getValue();
259    if (value == null)
260    {
261      throw new LDAPException(ResultCode.DECODING_ERROR,
262                              ERR_GER_REQUEST_NO_VALUE.get());
263    }
264
265    final ASN1Element[] elements;
266    try
267    {
268      final ASN1Element valueElement = ASN1Element.decode(value.getValue());
269      elements = ASN1Sequence.decodeAsSequence(valueElement).elements();
270    }
271    catch (final Exception e)
272    {
273      Debug.debugException(e);
274      throw new LDAPException(ResultCode.DECODING_ERROR,
275                              ERR_GER_REQUEST_VALUE_NOT_SEQUENCE.get(e), e);
276    }
277
278    if ((elements.length < 1) || (elements.length > 2))
279    {
280      throw new LDAPException(ResultCode.DECODING_ERROR,
281                              ERR_GER_REQUEST_INVALID_ELEMENT_COUNT.get(
282                                   elements.length));
283    }
284
285    authzID = ASN1OctetString.decodeAsOctetString(elements[0]).stringValue();
286
287    if (elements.length == 2)
288    {
289      try
290      {
291        final ASN1Element[] attrElements =
292             ASN1Sequence.decodeAsSequence(elements[1]).elements();
293        attributes = new String[attrElements.length];
294        for (int i=0; i < attrElements.length; i++)
295        {
296          attributes[i] = ASN1OctetString.decodeAsOctetString(
297                               attrElements[i]).stringValue();
298        }
299      }
300      catch (final Exception e)
301      {
302        Debug.debugException(e);
303        throw new LDAPException(ResultCode.DECODING_ERROR,
304                                ERR_GER_REQUEST_CANNOT_DECODE.get(e), e);
305      }
306    }
307    else
308    {
309      attributes = StaticUtils.NO_STRINGS;
310    }
311  }
312
313
314
315  /**
316   * Encodes the provided information into an ASN.1 octet string suitable for
317   * use as the value of this control.
318   *
319   * @param  authzID     The authorization ID of the user for whom the effective
320   *                     rights should be calculated.  It must not be
321   *                     {@code null}.
322   * @param  attributes  The set of attributes for which to calculate the
323   *                     effective rights.
324   *
325   * @return  An ASN.1 octet string containing the encoded control value.
326   */
327  @NotNull()
328  private static ASN1OctetString encodeValue(@NotNull final String authzID,
329                      @Nullable final String[] attributes)
330  {
331    Validator.ensureNotNull(authzID);
332
333    final ASN1Element[] elements;
334    if ((attributes == null) || (attributes.length == 0))
335    {
336      elements = new ASN1Element[]
337      {
338        new ASN1OctetString(authzID),
339        new ASN1Sequence()
340      };
341    }
342    else
343    {
344      final ASN1Element[] attrElements = new ASN1Element[attributes.length];
345      for (int i=0; i < attributes.length; i++)
346      {
347        attrElements[i] = new ASN1OctetString(attributes[i]);
348      }
349
350      elements = new ASN1Element[]
351      {
352        new ASN1OctetString(authzID),
353        new ASN1Sequence(attrElements)
354      };
355    }
356
357    return new ASN1OctetString(new ASN1Sequence(elements).encode());
358  }
359
360
361
362  /**
363   * Retrieves the authorization ID of the user for whom to calculate the
364   * effective rights.
365   *
366   * @return  The authorization ID of the user for whom to calculate the
367   *          effective rights.
368   */
369  @NotNull()
370  public String getAuthzID()
371  {
372    return authzID;
373  }
374
375
376
377  /**
378   * Retrieves the names of the attributes for which to calculate the effective
379   * rights information.
380   *
381   * @return  The names of the attributes for which to calculate the effective
382   *          rights information, or an empty array if no attribute names were
383   *          specified.
384   */
385  @NotNull()
386  public String[] getAttributes()
387  {
388    return attributes;
389  }
390
391
392
393  /**
394   * {@inheritDoc}
395   */
396  @Override()
397  @NotNull()
398  public String getControlName()
399  {
400    return INFO_CONTROL_NAME_GET_EFFECTIVE_RIGHTS_REQUEST.get();
401  }
402
403
404
405  /**
406   * Retrieves a representation of this get effective rights request control as
407   * a JSON object.  The JSON object uses the following fields:
408   * <UL>
409   *   <LI>
410   *     {@code oid} -- A mandatory string field whose value is the object
411   *     identifier for this control.  For the get effective rights request
412   *     control, the OID is "1.3.6.1.4.1.42.2.27.9.5.2".
413   *   </LI>
414   *   <LI>
415   *     {@code control-name} -- An optional string field whose value is a
416   *     human-readable name for this control.  This field is only intended for
417   *     descriptive purposes, and when decoding a control, the {@code oid}
418   *     field should be used to identify the type of control.
419   *   </LI>
420   *   <LI>
421   *     {@code criticality} -- A mandatory Boolean field used to indicate
422   *     whether this control is considered critical.
423   *   </LI>
424   *   <LI>
425   *     {@code value-base64} -- An optional string field whose value is a
426   *     base64-encoded representation of the raw value for this get effective
427   *     rights request control.  Exactly one of the {@code value-base64} and
428   *     {@code value-json} fields must be present.
429   *   </LI>
430   *   <LI>
431   *     {@code value-json} -- An optional JSON object field whose value is a
432   *     user-friendly representation of the value for this get effective rights
433   *     request control.  Exactly one of the {@code value-base64} and
434   *     {@code value-json} fields must be present, and if the
435   *     {@code value-json} field is used, then it will use the following
436   *     fields:
437   *     <UL>
438   *       <LI>
439   *         {@code authorization-id} -- A mandatory string field whose value is
440   *         the authorization identity of the user for whom to retrieve the
441   *         effective rights.
442   *       </LI>
443   *       <LI>
444   *         {@code attributes} -- An optional array field whose values are
445   *         strings that represent the names of the attributes for which to
446   *         make the effective rights determination.
447   *       </LI>
448   *     </UL>
449   *   </LI>
450   * </UL>
451   *
452   * @return  A JSON object that contains a representation of this control.
453   */
454  @Override()
455  @NotNull()
456  public JSONObject toJSONControl()
457  {
458    final Map<String,JSONValue> valueFields = new LinkedHashMap<>();
459    valueFields.put(JSON_FIELD_AUTHORIZATION_ID, new JSONString(authzID));
460
461    if (attributes.length > 0)
462    {
463      final List<JSONValue> attributeValues = new ArrayList<>();
464      for (final String attribute : attributes)
465      {
466        attributeValues.add(new JSONString(attribute));
467      }
468
469      valueFields.put(JSON_FIELD_ATTRIBUTES, new JSONArray(attributeValues));
470    }
471
472    return new JSONObject(
473         new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID,
474              GET_EFFECTIVE_RIGHTS_REQUEST_OID),
475         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME,
476              INFO_CONTROL_NAME_GET_EFFECTIVE_RIGHTS_REQUEST.get()),
477         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY,
478              isCritical()),
479         new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON,
480              new JSONObject(valueFields)));
481  }
482
483
484
485  /**
486   * Attempts to decode the provided object as a JSON representation of a get
487   * effective rights request control.
488   *
489   * @param  controlObject  The JSON object to be decoded.  It must not be
490   *                        {@code null}.
491   * @param  strict         Indicates whether to use strict mode when decoding
492   *                        the provided JSON object.  If this is {@code true},
493   *                        then this method will throw an exception if the
494   *                        provided JSON object contains any unrecognized
495   *                        fields.  If this is {@code false}, then unrecognized
496   *                        fields will be ignored.
497   *
498   * @return  The get effective rights request control that was decoded from
499   *          the provided JSON object.
500   *
501   * @throws  LDAPException  If the provided JSON object cannot be parsed as a
502   *                         valid get effective rights request control.
503   */
504  @NotNull()
505  public static GetEffectiveRightsRequestControl decodeJSONControl(
506              @NotNull final JSONObject controlObject,
507              final boolean strict)
508         throws LDAPException
509  {
510    final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper(
511         controlObject, strict, true, true);
512
513    final ASN1OctetString rawValue = jsonControl.getRawValue();
514    if (rawValue != null)
515    {
516      return new GetEffectiveRightsRequestControl(new Control(
517           jsonControl.getOID(), jsonControl.getCriticality(), rawValue));
518    }
519
520
521    final JSONObject valueObject = jsonControl.getValueObject();
522
523    final String authorizationID =
524         valueObject.getFieldAsString(JSON_FIELD_AUTHORIZATION_ID);
525    if (authorizationID == null)
526    {
527      throw new LDAPException(ResultCode.DECODING_ERROR,
528           ERR_GER_REQUEST_JSON_MISSING_AUTHZ_ID.get(
529                controlObject.toSingleLineString(),
530                JSON_FIELD_AUTHORIZATION_ID));
531    }
532
533    final String[] attributes;
534    final List<JSONValue> attrValues =
535         valueObject.getFieldAsArray(JSON_FIELD_ATTRIBUTES);
536    if (attrValues == null)
537    {
538      attributes = StaticUtils.NO_STRINGS;
539    }
540    else
541    {
542      attributes = new String[attrValues.size()];
543      for (int i=0; i < attributes.length; i++)
544      {
545        final JSONValue v = attrValues.get(i);
546        if (v instanceof JSONString)
547        {
548          attributes[i] = ((JSONString) v).stringValue();
549        }
550        else
551        {
552          throw new LDAPException(ResultCode.DECODING_ERROR,
553               ERR_GER_REQUEST_JSON_ATTR_NOT_STRING.get(
554                    controlObject.toSingleLineString(),
555                    JSON_FIELD_ATTRIBUTES));
556        }
557      }
558    }
559
560
561    if (strict)
562    {
563      final List<String> unrecognizedFields =
564           JSONControlDecodeHelper.getControlObjectUnexpectedFields(
565                valueObject, JSON_FIELD_AUTHORIZATION_ID,
566                JSON_FIELD_ATTRIBUTES);
567      if (! unrecognizedFields.isEmpty())
568      {
569        throw new LDAPException(ResultCode.DECODING_ERROR,
570             ERR_GER_REQUEST_JSON_CONTROL_UNRECOGNIZED_FIELD.get(
571                  controlObject.toSingleLineString(),
572                  unrecognizedFields.get(0)));
573      }
574    }
575
576
577    return new GetEffectiveRightsRequestControl(jsonControl.getCriticality(),
578         authorizationID, attributes);
579  }
580
581
582
583  /**
584   * {@inheritDoc}
585   */
586  @Override()
587  public void toString(@NotNull final StringBuilder buffer)
588  {
589    buffer.append("GetEffectiveRightsRequestControl(authzId='");
590    buffer.append(authzID);
591    buffer.append('\'');
592
593    if (attributes.length > 0)
594    {
595      buffer.append(", attributes={");
596      for (int i=0; i < attributes.length; i++)
597      {
598        if (i > 0)
599        {
600          buffer.append(", ");
601        }
602
603        buffer.append(attributes[i]);
604      }
605      buffer.append('}');
606    }
607
608    buffer.append(", isCritical=");
609    buffer.append(isCritical());
610    buffer.append(')');
611  }
612}