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