001/*
002 * Copyright 2007-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-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) 2007-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.controls;
037
038
039
040import java.util.List;
041
042import com.unboundid.asn1.ASN1Element;
043import com.unboundid.asn1.ASN1OctetString;
044import com.unboundid.asn1.ASN1Sequence;
045import com.unboundid.ldap.sdk.Control;
046import com.unboundid.ldap.sdk.DN;
047import com.unboundid.ldap.sdk.JSONControlDecodeHelper;
048import com.unboundid.ldap.sdk.LDAPException;
049import com.unboundid.ldap.sdk.ResultCode;
050import com.unboundid.util.Debug;
051import com.unboundid.util.NotMutable;
052import com.unboundid.util.NotNull;
053import com.unboundid.util.ThreadSafety;
054import com.unboundid.util.ThreadSafetyLevel;
055import com.unboundid.util.Validator;
056import com.unboundid.util.json.JSONField;
057import com.unboundid.util.json.JSONObject;
058
059import static com.unboundid.ldap.sdk.controls.ControlMessages.*;
060
061
062
063/**
064 * This class provides an implementation of the proxied authorization V1
065 * request control, which may be used to request that the associated operation
066 * be performed as if it had been requested by some other user.  It is based on
067 * the specification provided in early versions of the
068 * draft-weltman-ldapv3-proxy Internet Draft (this implementation is based on
069 * the "-04" revision).  Later versions of the draft, and subsequently
070 * <A HREF="http://www.ietf.org/rfc/rfc4370.txt">RFC 4370</A>, define a second
071 * version of the proxied authorization control with a different OID and
072 * different value format.  This control is supported primarily for legacy
073 * purposes, and it is recommended that new applications use the
074 * {@link ProxiedAuthorizationV2RequestControl} instead if this version.
075 * <BR><BR>
076 * The value of this control includes the DN of the user as whom the operation
077 * should be performed.  Note that it should be a distinguished name, and not an
078 * authzId value as is used in the proxied authorization V2 control.
079 * <BR><BR>
080 * This control may be used in conjunction with add, delete, compare, delete,
081 * extended, modify, modify DN, and search requests.  In that case, the
082 * associated operation will be processed under the authority of the specified
083 * authorization identity rather than the identity associated with the client
084 * connection (i.e., the user as whom that connection is bound).  Note that
085 * because of the inherent security risks associated with the use of the proxied
086 * authorization control, most directory servers which support its use enforce
087 * strict restrictions on the users that are allowed to request this control.
088 * Note that while the directory server should return a
089 * {@link ResultCode#AUTHORIZATION_DENIED} result for a proxied authorization V2
090 * control if the requester does not have the appropriate permission to use that
091 * control, this result will not necessarily be used for the same condition with
092 * the proxied authorization V1 control because this result code was not defined
093 * until the release of the proxied authorization V2 specification.
094 * code.
095 * <BR><BR>
096 * There is no corresponding response control for this request control.
097 * <BR><BR>
098 * <H2>Example</H2>
099 * The following example demonstrates the use of the proxied authorization V1
100 * control to delete an entry under the authority of the user with DN
101 * "uid=alternate.user,ou=People,dc=example,dc=com":
102 * <PRE>
103 * // Create a delete request to delete an entry.  Include the proxied
104 * // authorization v1 request control in the delete request so that the
105 * // delete will be processed as user
106 * // "uid=alternate.user,ou=People,dc=example,dc=com" instead of the user
107 * // that's actually authenticated on the connection.
108 * DeleteRequest deleteRequest =
109 *      new DeleteRequest("uid=test.user,ou=People,dc=example,dc=com");
110 * deleteRequest.addControl(new ProxiedAuthorizationV1RequestControl(
111 *      "uid=alternate.user,ou=People,dc=example,dc=com"));
112 *
113 * LDAPResult deleteResult;
114 * try
115 * {
116 *   deleteResult = connection.delete(deleteRequest);
117 *   // If we got here, then the delete was successful.
118 * }
119 * catch (LDAPException le)
120 * {
121 *   // The delete failed for some reason.  In addition to all of the normal
122 *   // reasons a delete could fail (e.g., the entry doesn't exist, or has one
123 *   // or more subordinates), proxied-authorization specific failures may
124 *   // include that the authenticated user doesn't have permission to use the
125 *   // proxied authorization control to impersonate the alternate user, that
126 *   // the alternate user doesn't exist, or that the alternate user doesn't
127 *   // have permission to perform the requested operation.
128 *   deleteResult = le.toLDAPResult();
129 *   ResultCode resultCode = le.getResultCode();
130 *   String errorMessageFromServer = le.getDiagnosticMessage();
131 * }
132 * </PRE>
133 */
134@NotMutable()
135@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
136public final class ProxiedAuthorizationV1RequestControl
137       extends Control
138{
139  /**
140   * The OID (2.16.840.1.113730.3.4.12) for the proxied authorization v1 request
141   * control.
142   */
143  @NotNull public static final String PROXIED_AUTHORIZATION_V1_REQUEST_OID =
144       "2.16.840.1.113730.3.4.12";
145
146
147
148  /**
149   * The name of the field used to hold the authorization DN in the JSON
150   * representation of this control.
151   */
152  @NotNull private static final String JSON_FIELD_AUTHORIZATION_DN =
153       "authorization-dn";
154
155
156
157  /**
158   * The serial version UID for this serializable class.
159   */
160  private static final long serialVersionUID = 7312632337431962774L;
161
162
163
164  // The DN of the target user under whose authorization the associated
165  // operation should be performed.
166  @NotNull private final String proxyDN;
167
168
169
170  /**
171   * Creates a new proxied authorization V1 request control that will proxy as
172   * the specified user.
173   *
174   * @param  proxyDN  The DN of the target user under whose authorization the
175   *                  associated request should be performed.  It must not be
176   *                  {@code null}, although it may be an empty string to
177   *                  request an anonymous authorization.
178   */
179  public ProxiedAuthorizationV1RequestControl(@NotNull final String proxyDN)
180  {
181    super(PROXIED_AUTHORIZATION_V1_REQUEST_OID, true, encodeValue(proxyDN));
182
183    Validator.ensureNotNull(proxyDN);
184
185    this.proxyDN = proxyDN;
186  }
187
188
189
190  /**
191   * Creates a new proxied authorization V1 request control that will proxy as
192   * the specified user.
193   *
194   * @param  proxyDN  The DN of the target user under whose authorization the
195   *                  associated request should be performed.  It must not be
196   *                  {@code null}.
197   */
198  public ProxiedAuthorizationV1RequestControl(@NotNull final DN proxyDN)
199  {
200    super(PROXIED_AUTHORIZATION_V1_REQUEST_OID, true,
201          encodeValue(proxyDN.toString()));
202
203    this.proxyDN = proxyDN.toString();
204  }
205
206
207
208  /**
209   * Creates a new proxied authorization v1 request control which is decoded
210   * from the provided generic control.
211   *
212   * @param  control  The generic control to be decoded as a proxied
213   *                  authorization v1 request control.
214   *
215   * @throws  LDAPException  If the provided control cannot be decoded as a
216   *                         proxied authorization v1 request control.
217   */
218  public ProxiedAuthorizationV1RequestControl(@NotNull final Control control)
219         throws LDAPException
220  {
221    super(control);
222
223    final ASN1OctetString value = control.getValue();
224    if (value == null)
225    {
226      throw new LDAPException(ResultCode.DECODING_ERROR,
227                              ERR_PROXY_V1_NO_VALUE.get());
228    }
229
230    try
231    {
232      final ASN1Element valueElement = ASN1Element.decode(value.getValue());
233      final ASN1Element[] elements =
234           ASN1Sequence.decodeAsSequence(valueElement).elements();
235      proxyDN = ASN1OctetString.decodeAsOctetString(elements[0]).stringValue();
236    }
237    catch (final Exception e)
238    {
239      Debug.debugException(e);
240      throw new LDAPException(ResultCode.DECODING_ERROR,
241                              ERR_PROXYV1_DECODE_ERROR.get(e), e);
242    }
243  }
244
245
246
247  /**
248   * Encodes the provided information into an octet string that can be used as
249   * the value for this control.
250   *
251   * @param  proxyDN  The DN of the target user under whose authorization the
252   *                  associated request should be performed.  It must not be
253   *                  {@code null}, although it may be an empty string to
254   *                  request an anonymous authorization.
255   *
256   * @return  An ASN.1 octet string that can be used as the value for this
257   *          control.
258   */
259  @NotNull()
260  private static ASN1OctetString encodeValue(@NotNull final String proxyDN)
261  {
262    final ASN1Element[] valueElements =
263    {
264      new ASN1OctetString(proxyDN)
265    };
266
267    return new ASN1OctetString(new ASN1Sequence(valueElements).encode());
268  }
269
270
271
272  /**
273   * Retrieves the DN of the target user under whose authorization the
274   * associated request should be performed.
275   *
276   * @return  The DN of the target user under whose authorization the associated
277   *          request should be performed.
278   */
279  @NotNull()
280  public String getProxyDN()
281  {
282    return proxyDN;
283  }
284
285
286
287  /**
288   * {@inheritDoc}
289   */
290  @Override()
291  @NotNull()
292  public String getControlName()
293  {
294    return INFO_CONTROL_NAME_PROXIED_AUTHZ_V1_REQUEST.get();
295  }
296
297
298
299  /**
300   * Retrieves a representation of this proxied authorization v1 request control
301   * as a JSON object.  The JSON object uses the following fields:
302   * <UL>
303   *   <LI>
304   *     {@code oid} -- A mandatory string field whose value is the object
305   *     identifier for this control.  For the proxied authorization v1 request
306   *     control, the OID is "2.16.840.1.113730.3.4.12".
307   *   </LI>
308   *   <LI>
309   *     {@code control-name} -- An optional string field whose value is a
310   *     human-readable name for this control.  This field is only intended for
311   *     descriptive purposes, and when decoding a control, the {@code oid}
312   *     field should be used to identify the type of control.
313   *   </LI>
314   *   <LI>
315   *     {@code criticality} -- A mandatory Boolean field used to indicate
316   *     whether this control is considered critical.
317   *   </LI>
318   *   <LI>
319   *     {@code value-base64} -- An optional string field whose value is a
320   *     base64-encoded representation of the raw value for this proxied
321   *     authorization v1 request control.  Exactly one of the
322   *     {@code value-base64} and {@code value-json} fields must be present.
323   *   </LI>
324   *   <LI>
325   *     {@code value-json} -- An optional JSON object field whose value is a
326   *     user-friendly representation of the value for this proxied
327   *     authorization v1 request control.  Exactly one of the
328   *     {@code value-base64} and {@code value-json} fields must be present, and
329   *     if the {@code value-json} field is used, then it will use the following
330   *     fields:
331   *     <UL>
332   *       <LI>
333   *         {@code authorization-dn} -- A mandatory string field whose value is
334   *         the DN of the user as whom the request should be authorized.
335   *       </LI>
336   *     </UL>
337   *   </LI>
338   * </UL>
339   *
340   * @return  A JSON object that contains a representation of this control.
341   */
342  @Override()
343  @NotNull()
344  public JSONObject toJSONControl()
345  {
346    return new JSONObject(
347         new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID,
348              PROXIED_AUTHORIZATION_V1_REQUEST_OID),
349         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME,
350              INFO_CONTROL_NAME_PROXIED_AUTHZ_V1_REQUEST.get()),
351         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY,
352              isCritical()),
353         new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON,
354              new JSONObject(
355                   new JSONField(JSON_FIELD_AUTHORIZATION_DN, proxyDN))));
356  }
357
358
359
360  /**
361   * Attempts to decode the provided object as a JSON representation of a
362   * proxied authorization v1 request control.
363   *
364   * @param  controlObject  The JSON object to be decoded.  It must not be
365   *                        {@code null}.
366   * @param  strict         Indicates whether to use strict mode when decoding
367   *                        the provided JSON object.  If this is {@code true},
368   *                        then this method will throw an exception if the
369   *                        provided JSON object contains any unrecognized
370   *                        fields.  If this is {@code false}, then unrecognized
371   *                        fields will be ignored.
372   *
373   * @return  The proxied authorization v1 request control that was decoded from
374   *          the provided JSON object.
375   *
376   * @throws  LDAPException  If the provided JSON object cannot be parsed as a
377   *                         valid proxied authorization v1 request control.
378   */
379  @NotNull()
380  public static ProxiedAuthorizationV1RequestControl decodeJSONControl(
381              @NotNull final JSONObject controlObject,
382              final boolean strict)
383         throws LDAPException
384  {
385    final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper(
386         controlObject, strict, true, true);
387
388    final ASN1OctetString rawValue = jsonControl.getRawValue();
389    if (rawValue != null)
390    {
391      return new ProxiedAuthorizationV1RequestControl(new Control(
392           jsonControl.getOID(), jsonControl.getCriticality(), rawValue));
393    }
394
395
396    final JSONObject valueObject = jsonControl.getValueObject();
397
398    final String authorizationDN =
399         valueObject.getFieldAsString(JSON_FIELD_AUTHORIZATION_DN);
400    if (authorizationDN == null)
401    {
402      throw new LDAPException(ResultCode.DECODING_ERROR,
403           ERR_PROXYV1_JSON_MISSING_AUTHZ_DN.get(
404                controlObject.toSingleLineString(),
405                JSON_FIELD_AUTHORIZATION_DN));
406    }
407
408
409    if (strict)
410    {
411      final List<String> unrecognizedFields =
412           JSONControlDecodeHelper.getControlObjectUnexpectedFields(
413                valueObject, JSON_FIELD_AUTHORIZATION_DN);
414      if (! unrecognizedFields.isEmpty())
415      {
416        throw new LDAPException(ResultCode.DECODING_ERROR,
417             ERR_PROXYV1_JSON_UNRECOGNIZED_FIELD.get(
418                  controlObject.toSingleLineString(),
419                  unrecognizedFields.get(0)));
420      }
421    }
422
423
424    return new ProxiedAuthorizationV1RequestControl(authorizationDN);
425  }
426
427
428
429  /**
430   * {@inheritDoc}
431   */
432  @Override()
433  public void toString(@NotNull final StringBuilder buffer)
434  {
435    buffer.append("ProxiedAuthorizationV1RequestControl(proxyDN='");
436    buffer.append(proxyDN);
437    buffer.append("')");
438  }
439}