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