001/*
002 * Copyright 2011-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2011-2022 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) 2011-2022 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;
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.LDAPException;
047import com.unboundid.ldap.sdk.ResultCode;
048import com.unboundid.util.Debug;
049import com.unboundid.util.NotMutable;
050import com.unboundid.util.NotNull;
051import com.unboundid.util.Nullable;
052import com.unboundid.util.StaticUtils;
053import com.unboundid.util.ThreadSafety;
054import com.unboundid.util.ThreadSafetyLevel;
055import com.unboundid.util.Validator;
056
057import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
058
059
060
061/**
062 * This class provides a request control that can be used by the client to
063 * identify the purpose of the associated operation.  It can be used in
064 * conjunction with any kind of operation, and may be used to provide
065 * information about the reason for that operation, as well as about the client
066 * application used to generate the request.  This may be very useful for
067 * debugging and auditing purposes.
068 * <BR>
069 * <BLOCKQUOTE>
070 *   <B>NOTE:</B>  This class, and other classes within the
071 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
072 *   supported for use against Ping Identity, UnboundID, and
073 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
074 *   for proprietary functionality or for external specifications that are not
075 *   considered stable or mature enough to be guaranteed to work in an
076 *   interoperable way with other types of LDAP servers.
077 * </BLOCKQUOTE>
078 * <BR>
079 * The criticality for this control may be either {@code true} or {@code false}.
080 * It must have a value with the following encoding:
081 * <PRE>
082 *   OperationPurposeRequest ::= SEQUENCE {
083 *        applicationName     [0] OCTET STRING OPTIONAL,
084 *        applicationVersion  [1] OCTET STRING OPTIONAL,
085 *        codeLocation        [2] OCTET STRING OPTIONAL,
086 *        requestPurpose      [3] OCTET STRING OPTIONAL
087 *        ... }
088 * </PRE>
089 * At least one of the elements in the value sequence must be present.
090 * <BR><BR>
091 * <H2>Example</H2>
092 * The following example demonstrates a sample authentication consisting of a
093 * search to find a user followed by a bind to verify that user's password.
094 * Both the search and bind requests will include operation purpose controls
095 * with information about the reason for the request.  Note that for the sake
096 * of brevity and clarity, error handling has been omitted from this example.
097 * <PRE>
098 * SearchRequest searchRequest = new SearchRequest("dc=example,dc=com",
099 *      SearchScope.SUB, Filter.createEqualityFilter("uid", uidValue),
100 *      "1.1");
101 * searchRequest.addControl(new OperationPurposeRequestControl(appName,
102 *      appVersion, 0,  "Retrieve the entry for a user with a given uid"));
103 * Entry userEntry = connection.searchForEntry(searchRequest);
104 *
105 * SimpleBindRequest bindRequest = new SimpleBindRequest(userEntry.getDN(),
106 *      password, new OperationPurposeRequestControl(appName, appVersion, 0,
107 *      "Bind as a user to verify the provided credentials."));
108 * BindResult bindResult = connection.bind(bindRequest);
109 * </PRE>
110 */
111@NotMutable()
112@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
113public final class OperationPurposeRequestControl
114       extends Control
115{
116  /**
117   * The OID (1.3.6.1.4.1.30221.2.5.19) for the operation purpose request
118   * control.
119   */
120  @NotNull public static final String OPERATION_PURPOSE_REQUEST_OID =
121       "1.3.6.1.4.1.30221.2.5.19";
122
123
124
125  /**
126   * The BER type for the element that specifies the application name.
127   */
128  private static final byte TYPE_APP_NAME = (byte) 0x80;
129
130
131
132  /**
133   * The BER type for the element that specifies the application version.
134   */
135  private static final byte TYPE_APP_VERSION = (byte) 0x81;
136
137
138
139  /**
140   * The BER type for the element that specifies the code location.
141   */
142  private static final byte TYPE_CODE_LOCATION = (byte) 0x82;
143
144
145
146  /**
147   * The BER type for the element that specifies the request purpose.
148   */
149  private static final byte TYPE_REQUEST_PURPOSE = (byte) 0x83;
150
151
152
153  /**
154   * The serial version UID for this serializable class.
155   */
156  private static final long serialVersionUID = -5552051862785419833L;
157
158
159
160  // The application name for this control, if any.
161  @Nullable private final String applicationName;
162
163  // The application version for this control, if any.
164  @Nullable private final String applicationVersion;
165
166  // The code location for this control, if any.
167  @Nullable private final String codeLocation;
168
169  // The request purpose for this control, if any.
170  @Nullable private final String requestPurpose;
171
172
173
174  /**
175   * Creates a new operation purpose request control with the provided
176   * information.  It will not be critical.  If the generateCodeLocation
177   * argument has a value of {@code false}, then at least one of the
178   * applicationName, applicationVersion, and requestPurpose arguments must
179   * be non-{@code null}.
180   *
181   * @param  applicationName     The name of the application generating the
182   *                             associated request.  It may be {@code null} if
183   *                             this should not be included in the control.
184   * @param  applicationVersion  Information about the version of the
185   *                             application generating the associated request.
186   *                             It may be {@code null} if this should not be
187   *                             included in the control.
188   * @param  codeLocationFrames  Indicates that the code location should be
189   *                             automatically generated with a condensed stack
190   *                             trace for the current thread, using the
191   *                             specified number of stack frames.  A value that
192   *                             is less than or equal to zero indicates an
193   *                             unlimited number of stack frames should be
194   *                             included.
195   * @param  requestPurpose      A string identifying the purpose of the
196   *                             associated request.  It may be {@code null} if
197   *                             this should not be included in the control.
198   */
199  public OperationPurposeRequestControl(@Nullable final String applicationName,
200              @Nullable final String applicationVersion,
201              final int codeLocationFrames,
202              @Nullable final String requestPurpose)
203  {
204    this(false, applicationName, applicationVersion,
205         generateStackTrace(codeLocationFrames), requestPurpose);
206  }
207
208
209
210  /**
211   * Creates a new operation purpose request control with the provided
212   * information.  At least one of the applicationName, applicationVersion,
213   * codeLocation, and requestPurpose arguments must be non-{@code null}.
214   *
215   * @param  isCritical          Indicates whether the control should be
216   *                             considered critical.
217   * @param  applicationName     The name of the application generating the
218   *                             associated request.  It may be {@code null} if
219   *                             this should not be included in the control.
220   * @param  applicationVersion  Information about the version of the
221   *                             application generating the associated request.
222   *                             It may be {@code null} if this should not be
223   *                             included in the control.
224   * @param  codeLocation        Information about the location in the
225   *                             application code in which the associated
226   *                             request is generated (e.g., the class and/or
227   *                             method name, or any other useful identifier).
228   *                             It may be {@code null} if this should not be
229   *                             included in the control.
230   * @param  requestPurpose      A string identifying the purpose of the
231   *                             associated request.  It may be {@code null} if
232   *                             this should not be included in the control.
233   */
234  public OperationPurposeRequestControl(final boolean isCritical,
235              @Nullable final String applicationName,
236              @Nullable final String applicationVersion,
237              @Nullable final String codeLocation,
238              @Nullable final String requestPurpose)
239  {
240    super(OPERATION_PURPOSE_REQUEST_OID, isCritical,
241         encodeValue(applicationName, applicationVersion, codeLocation,
242              requestPurpose));
243
244    this.applicationName    = applicationName;
245    this.applicationVersion = applicationVersion;
246    this.codeLocation       = codeLocation;
247    this.requestPurpose     = requestPurpose;
248  }
249
250
251
252  /**
253   * Creates a new operation purpose request control which is decoded from the
254   * provided generic control.
255   *
256   * @param  control  The generic control to be decoded as an operation purpose
257   *                  request control.
258   *
259   * @throws  LDAPException  If the provided control cannot be decoded as an
260   *                         operation purpose request control.
261   */
262  public OperationPurposeRequestControl(@NotNull final Control control)
263         throws LDAPException
264  {
265    super(control);
266
267    final ASN1OctetString value = control.getValue();
268    if (value == null)
269    {
270      throw new LDAPException(ResultCode.DECODING_ERROR,
271           ERR_OP_PURPOSE_NO_VALUE.get());
272    }
273
274    final ASN1Element[] valueElements;
275    try
276    {
277      valueElements =
278           ASN1Sequence.decodeAsSequence(value.getValue()).elements();
279    }
280    catch (final Exception e)
281    {
282      Debug.debugException(e);
283      throw new LDAPException(ResultCode.DECODING_ERROR,
284           ERR_OP_PURPOSE_VALUE_NOT_SEQUENCE.get(
285                StaticUtils.getExceptionMessage(e)),
286           e);
287    }
288
289    if (valueElements.length == 0)
290    {
291      throw new LDAPException(ResultCode.DECODING_ERROR,
292           ERR_OP_PURPOSE_VALUE_SEQUENCE_EMPTY.get());
293    }
294
295
296    String appName    = null;
297    String appVersion = null;
298    String codeLoc    = null;
299    String reqPurpose = null;
300    for (final ASN1Element e : valueElements)
301    {
302      switch (e.getType())
303      {
304        case TYPE_APP_NAME:
305          appName = ASN1OctetString.decodeAsOctetString(e).stringValue();
306          break;
307
308        case TYPE_APP_VERSION:
309          appVersion = ASN1OctetString.decodeAsOctetString(e).stringValue();
310          break;
311
312        case TYPE_CODE_LOCATION:
313          codeLoc = ASN1OctetString.decodeAsOctetString(e).stringValue();
314          break;
315
316        case TYPE_REQUEST_PURPOSE:
317          reqPurpose = ASN1OctetString.decodeAsOctetString(e).stringValue();
318          break;
319
320        default:
321          throw new LDAPException(ResultCode.DECODING_ERROR,
322               ERR_OP_PURPOSE_VALUE_UNSUPPORTED_ELEMENT.get(
323                    StaticUtils.toHex(e.getType())));
324      }
325    }
326
327    applicationName    = appName;
328    applicationVersion = appVersion;
329    codeLocation       = codeLoc;
330    requestPurpose     = reqPurpose;
331  }
332
333
334
335  /**
336   * Generates a compact stack trace for the current thread,  The stack trace
337   * elements will start with the last frame to call into this class (so that
338   * frames referencing this class, and anything called by this class in the
339   * process of getting the stack trace will be omitted).  Elements will be
340   * space-delimited and will contain the unqualified class name, a period,
341   * the method name, a colon, and the source line number.
342   *
343   * @param  numFrames  The maximum number of frames to capture in the stack
344   *                    trace.
345   *
346   * @return  The generated stack trace for the current thread.
347   */
348  @NotNull()
349  private static String generateStackTrace(final int numFrames)
350  {
351    final StringBuilder buffer = new StringBuilder();
352    final int n = (numFrames > 0) ? numFrames : Integer.MAX_VALUE;
353
354    int c = 0;
355    boolean skip = true;
356    for (final StackTraceElement e : Thread.currentThread().getStackTrace())
357    {
358      final String className = e.getClassName();
359      if (className.equals(OperationPurposeRequestControl.class.getName()))
360      {
361        skip = false;
362        continue;
363      }
364      else if (skip)
365      {
366        continue;
367      }
368
369      if (buffer.length() > 0)
370      {
371        buffer.append(' ');
372      }
373
374      final int lastPeriodPos = className.lastIndexOf('.');
375      if (lastPeriodPos > 0)
376      {
377        buffer.append(className.substring(lastPeriodPos+1));
378      }
379      else
380      {
381        buffer.append(className);
382      }
383
384      buffer.append('.');
385      buffer.append(e.getMethodName());
386      buffer.append(':');
387      buffer.append(e.getLineNumber());
388
389      c++;
390      if (c >= n)
391      {
392        break;
393      }
394    }
395
396    return buffer.toString();
397  }
398
399
400
401  /**
402   * Encodes the provided information into a form suitable for use as the value
403   * of this control.
404   *
405   * @param  applicationName     The name of the application generating the
406   *                             associated request.  It may be {@code null} if
407   *                             this should not be included in the control.
408   * @param  applicationVersion  Information about the version of the
409   *                             application generating the associated request.
410   *                             It may be {@code null} if this should not be
411   *                             included in the control.
412   * @param  codeLocation        Information about the location in the
413   *                             application code in which the associated
414   *                             request is generated (e.g., the class and/or
415   *                             method name, or any other useful identifier).
416   *                             It may be {@code null} if this should not be
417   *                             included in the control.
418   * @param  requestPurpose      A string identifying the purpose of the
419   *                             associated request.  It may be {@code null} if
420   *                             this should not be included in the control.
421   *
422   * @return  The encoded value for this control.
423   */
424  @NotNull()
425  private static ASN1OctetString encodeValue(
426               @Nullable final String applicationName,
427               @Nullable final String applicationVersion,
428               @Nullable final String codeLocation,
429               @Nullable final String requestPurpose)
430  {
431    Validator.ensureFalse((applicationName == null) &&
432         (applicationVersion == null) && (codeLocation == null) &&
433         (requestPurpose == null));
434
435    final ArrayList<ASN1Element> elements = new ArrayList<>(4);
436
437    if (applicationName != null)
438    {
439      elements.add(new ASN1OctetString(TYPE_APP_NAME, applicationName));
440    }
441
442    if (applicationVersion != null)
443    {
444      elements.add(new ASN1OctetString(TYPE_APP_VERSION, applicationVersion));
445    }
446
447    if (codeLocation != null)
448    {
449      elements.add(new ASN1OctetString(TYPE_CODE_LOCATION, codeLocation));
450    }
451
452    if (requestPurpose != null)
453    {
454      elements.add(new ASN1OctetString(TYPE_REQUEST_PURPOSE, requestPurpose));
455    }
456
457    return new ASN1OctetString(new ASN1Sequence(elements).encode());
458  }
459
460
461
462  /**
463   * Retrieves the name of the application that generated the associated
464   * request, if available.
465   *
466   * @return  The name of the application that generated the associated request,
467   *          or {@code null} if that is not available.
468   */
469  @Nullable()
470  public String getApplicationName()
471  {
472    return applicationName;
473  }
474
475
476
477  /**
478   * Retrieves information about the version of the application that generated
479   * the associated request, if available.
480   *
481   * @return  Information about the version of the application that generated
482   *          the associated request, or {@code null} if that is not available.
483   */
484  @Nullable()
485  public String getApplicationVersion()
486  {
487    return applicationVersion;
488  }
489
490
491
492  /**
493   * Retrieves information about the location in the application code in which
494   * the associated request was created, if available.
495   *
496   * @return  Information about the location in the application code in which
497   *          the associated request was created, or {@code null} if that is not
498   *          available.
499   */
500  @Nullable()
501  public String getCodeLocation()
502  {
503    return codeLocation;
504  }
505
506
507
508  /**
509   * Retrieves a message with information about the purpose of the associated
510   * request, if available.
511   *
512   * @return  A message with information about the purpose of the associated
513   *          request, or {@code null} if that is not available.
514   */
515  @Nullable()
516  public String getRequestPurpose()
517  {
518    return requestPurpose;
519  }
520
521
522
523  /**
524   * {@inheritDoc}
525   */
526  @Override()
527  @NotNull()
528  public String getControlName()
529  {
530    return INFO_CONTROL_NAME_OP_PURPOSE.get();
531  }
532
533
534
535  /**
536   * {@inheritDoc}
537   */
538  @Override()
539  public void toString(@NotNull final StringBuilder buffer)
540  {
541    buffer.append("OperationPurposeRequestControl(isCritical=");
542    buffer.append(isCritical());
543
544    if (applicationName != null)
545    {
546      buffer.append(", appName='");
547      buffer.append(applicationName);
548      buffer.append('\'');
549    }
550
551
552    if (applicationVersion != null)
553    {
554      buffer.append(", appVersion='");
555      buffer.append(applicationVersion);
556      buffer.append('\'');
557    }
558
559
560    if (codeLocation != null)
561    {
562      buffer.append(", codeLocation='");
563      buffer.append(codeLocation);
564      buffer.append('\'');
565    }
566
567
568    if (requestPurpose != null)
569    {
570      buffer.append(", purpose='");
571      buffer.append(requestPurpose);
572      buffer.append('\'');
573    }
574
575    buffer.append(')');
576  }
577}