001/* 002 * Copyright 2016-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2016-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) 2016-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.experimental; 037 038 039 040import java.util.ArrayList; 041import java.util.Collections; 042import java.util.Date; 043import java.util.List; 044 045import com.unboundid.asn1.ASN1Sequence; 046import com.unboundid.ldap.sdk.Control; 047import com.unboundid.ldap.sdk.Entry; 048import com.unboundid.ldap.sdk.LDAPException; 049import com.unboundid.ldap.sdk.LDAPResult; 050import com.unboundid.ldap.sdk.OperationType; 051import com.unboundid.ldap.sdk.ReadOnlyEntry; 052import com.unboundid.ldap.sdk.ResultCode; 053import com.unboundid.util.Debug; 054import com.unboundid.util.NotExtensible; 055import com.unboundid.util.NotNull; 056import com.unboundid.util.Nullable; 057import com.unboundid.util.StaticUtils; 058import com.unboundid.util.ThreadSafety; 059import com.unboundid.util.ThreadSafetyLevel; 060 061import static com.unboundid.ldap.sdk.experimental.ExperimentalMessages.*; 062 063 064 065/** 066 * This class serves as the base class for entries that hold information about 067 * operations processed by an LDAP server, much like LDAP-accessible access log 068 * messages. The format for the entries used in this implementation is 069 * described in draft-chu-ldap-logschema-00. 070 */ 071@NotExtensible() 072@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 073public abstract class DraftChuLDAPLogSchema00Entry 074 extends ReadOnlyEntry 075{ 076 /** 077 * The name of the attribute used to hold the DN of the authorization identity 078 * for the operation. 079 */ 080 @NotNull public static final String ATTR_AUTHORIZATION_IDENTITY_DN = 081 "reqAuthzID"; 082 083 084 085 /** 086 * The name of the attribute used to hold the diagnostic message the server 087 * included in the response to the client. 088 */ 089 @NotNull public static final String ATTR_DIAGNOSTIC_MESSAGE = "reqMessage"; 090 091 092 093 /** 094 * The name of the attribute used to hold the type of operation that was 095 * processed. For extended operation, the value will be 096 * "extended" followed by the OID of the extended request (e.g., 097 * "extended1.3.6.1.4.1.1466.20037" to indicate the StartTLS extended 098 * request). For all other operation types, this will be simply the name of 099 * the operation: abandon, add, bind, compare, delete, modify, modrdn, 100 * search, or unbind. 101 */ 102 @NotNull public static final String ATTR_OPERATION_TYPE = "reqType"; 103 104 105 106 /** 107 * The name of the attribute used to hold the time the server completed 108 * processing the operation. Values will be in generalized time format, but 109 * may be of a very high precision to ensure that each log entry has a 110 * unique end time. 111 */ 112 @NotNull public static final String ATTR_PROCESSING_END_TIME = "reqEnd"; 113 114 115 116 /** 117 * The name of the attribute used to hold the time the server started 118 * processing the operation. Values will be in generalized time format, but 119 * may be of a very high precision to ensure that each log entry has a 120 * unique start time. 121 */ 122 @NotNull public static final String ATTR_PROCESSING_START_TIME = "reqStart"; 123 124 125 126 /** 127 * The name of the attribute used to hold a referral URL the server included 128 * in the response to the client. 129 */ 130 @NotNull public static final String ATTR_REFERRAL_URL = "reqReferral"; 131 132 133 134 /** 135 * The name of the attribute used to hold information about a request control 136 * included in the request received from the client. 137 */ 138 @NotNull public static final String ATTR_REQUEST_CONTROL = "reqControls"; 139 140 141 142 /** 143 * The name of the attribute used to hold information about a response control 144 * included in the result returned to the client. 145 */ 146 @NotNull public static final String ATTR_RESPONSE_CONTROL = "reqRespControls"; 147 148 149 150 /** 151 * The name of the attribute used to hold the integer value of the result code 152 * the server included in the response to the client. 153 */ 154 @NotNull public static final String ATTR_RESULT_CODE = "reqResult"; 155 156 157 158 /** 159 * The name of the attribute used to hold a session identifier for a sequence 160 * of operations received on the same connection. 161 */ 162 @NotNull public static final String ATTR_SESSION_ID = "reqSession"; 163 164 165 166 /** 167 * The name of the attribute used to hold the DN of the entry targeted by the 168 * operation. For a search operation, this will be the search base DN. 169 */ 170 @NotNull public static final String ATTR_TARGET_ENTRY_DN = "reqDN"; 171 172 173 174 /** 175 * The serial version UID for this serializable class. 176 */ 177 private static final long serialVersionUID = -7279669732772403236L; 178 179 180 181 // The parsed processing end time for the operation. 182 @Nullable private final Date processingEndTimeDate; 183 184 // The parsed processing start time for the operation. 185 @NotNull private final Date processingStartTimeDate; 186 187 // A list of controls included in the request from the client. 188 @NotNull private final List<Control> requestControls; 189 190 // A list of controls included in the request from the client. 191 @NotNull private final List<Control> responseControls; 192 193 // A list of referral URLs returned to the client. 194 @NotNull private final List<String> referralURLs; 195 196 // The operation type for the log entry. 197 @NotNull private final OperationType operationType; 198 199 // The result code returned to the client. 200 @Nullable private final ResultCode resultCode; 201 202 // The DN of the account used as the authorization identity for the operation. 203 @Nullable private final String authorizationIdentityDN; 204 205 // The diagnostic message returned to the client. 206 @Nullable private final String diagnosticMessage; 207 208 // The string representation of the processing end time for the operation. 209 @Nullable private final String processingEndTimeString; 210 211 // The string representation of the processing start time for the operation. 212 @NotNull private final String processingStartTimeString; 213 214 // The session ID for the sequence of operations received on the same 215 // connection. 216 @NotNull private final String sessionID; 217 218 // The DN of the entry targeted by the client. 219 @Nullable private final String targetEntryDN; 220 221 222 223 /** 224 * Creates a new instance of this access log entry from the provided entry. 225 * 226 * @param entry The entry used to create this access log entry. 227 * @param operationType The associated operation type. 228 * 229 * @throws LDAPException If the provided entry cannot be decoded as a valid 230 * access log entry as per the specification contained 231 * in draft-chu-ldap-logschema-00. 232 */ 233 DraftChuLDAPLogSchema00Entry(@NotNull final Entry entry, 234 @NotNull final OperationType operationType) 235 throws LDAPException 236 { 237 super(entry); 238 239 this.operationType = operationType; 240 241 242 // Get the processing start time. 243 processingStartTimeString = 244 entry.getAttributeValue(ATTR_PROCESSING_START_TIME); 245 if (processingStartTimeString == null) 246 { 247 throw new LDAPException(ResultCode.DECODING_ERROR, 248 ERR_LOGSCHEMA_DECODE_MISSING_REQUIRED_ATTR.get(entry.getDN(), 249 ATTR_PROCESSING_START_TIME)); 250 } 251 else 252 { 253 try 254 { 255 processingStartTimeDate = 256 StaticUtils.decodeGeneralizedTime(processingStartTimeString); 257 } 258 catch (final Exception e) 259 { 260 Debug.debugException(e); 261 throw new LDAPException(ResultCode.DECODING_ERROR, 262 ERR_LOGSCHEMA_DECODE_CANNOT_DECODE_TIME.get(entry.getDN(), 263 ATTR_PROCESSING_START_TIME, processingStartTimeString), 264 e); 265 } 266 } 267 268 269 // Get the processing end time. 270 processingEndTimeString = 271 entry.getAttributeValue(ATTR_PROCESSING_END_TIME); 272 if (processingEndTimeString == null) 273 { 274 processingEndTimeDate = null; 275 } 276 else 277 { 278 try 279 { 280 processingEndTimeDate = 281 StaticUtils.decodeGeneralizedTime(processingEndTimeString); 282 } 283 catch (final Exception e) 284 { 285 Debug.debugException(e); 286 throw new LDAPException(ResultCode.DECODING_ERROR, 287 ERR_LOGSCHEMA_DECODE_CANNOT_DECODE_TIME.get(entry.getDN(), 288 ATTR_PROCESSING_END_TIME, processingEndTimeString), 289 e); 290 } 291 } 292 293 294 // Get the session ID. 295 sessionID = entry.getAttributeValue(ATTR_SESSION_ID); 296 if (sessionID == null) 297 { 298 throw new LDAPException(ResultCode.DECODING_ERROR, 299 ERR_LOGSCHEMA_DECODE_MISSING_REQUIRED_ATTR.get(entry.getDN(), 300 ATTR_SESSION_ID)); 301 } 302 303 304 // Get the target DN. It can only be null for abandon, extended, and unbind 305 // operation types. 306 targetEntryDN = entry.getAttributeValue(ATTR_TARGET_ENTRY_DN); 307 if (targetEntryDN == null) 308 { 309 if (! ((operationType == OperationType.ABANDON) || 310 (operationType == OperationType.EXTENDED) || 311 (operationType == OperationType.UNBIND))) 312 { 313 throw new LDAPException(ResultCode.DECODING_ERROR, 314 ERR_LOGSCHEMA_DECODE_MISSING_REQUIRED_ATTR.get(entry.getDN(), 315 ATTR_TARGET_ENTRY_DN)); 316 } 317 } 318 319 320 // Get the authorization identity. 321 authorizationIdentityDN = 322 entry.getAttributeValue(ATTR_AUTHORIZATION_IDENTITY_DN); 323 324 325 // Get the set of request controls, if any. 326 requestControls = decodeControls(entry, ATTR_REQUEST_CONTROL); 327 328 329 // Get the set of response controls, if any. 330 responseControls = decodeControls(entry, ATTR_RESPONSE_CONTROL); 331 332 333 // Get the result code, if any. 334 final String resultCodeString = entry.getAttributeValue(ATTR_RESULT_CODE); 335 if (resultCodeString == null) 336 { 337 resultCode = null; 338 } 339 else 340 { 341 try 342 { 343 resultCode = ResultCode.valueOf(Integer.parseInt(resultCodeString)); 344 } 345 catch (final Exception e) 346 { 347 Debug.debugException(e); 348 throw new LDAPException(ResultCode.DECODING_ERROR, 349 ERR_LOGSCHEMA_DECODE_RESULT_CODE_ERROR.get(entry.getDN(), 350 resultCodeString, ATTR_RESULT_CODE), 351 e); 352 } 353 } 354 355 356 // Get the diagnostic message, if any. 357 diagnosticMessage = entry.getAttributeValue(ATTR_DIAGNOSTIC_MESSAGE); 358 359 360 // Get the referral URLs, if any. 361 final String[] referralArray = entry.getAttributeValues(ATTR_REFERRAL_URL); 362 if (referralArray == null) 363 { 364 referralURLs = Collections.emptyList(); 365 } 366 else 367 { 368 referralURLs = 369 Collections.unmodifiableList(StaticUtils.toList(referralArray)); 370 } 371 } 372 373 374 375 /** 376 * Decodes a set of controls contained in the specified attribute of the 377 * provided entry. 378 * 379 * @param entry The entry containing the controls to decode. 380 * @param attributeName The name of the attribute expected to hold the set 381 * of controls to decode. 382 * 383 * @return The decoded controls, or an empty list if the provided entry did 384 * not include any controls in the specified attribute. 385 * 386 * @throws LDAPException If a problem is encountered while trying to decode 387 * the controls. 388 */ 389 @NotNull() 390 private static List<Control> decodeControls(@NotNull final Entry entry, 391 @NotNull final String attributeName) 392 throws LDAPException 393 { 394 final byte[][] values = entry.getAttributeValueByteArrays(attributeName); 395 if ((values == null) || (values.length == 0)) 396 { 397 return Collections.emptyList(); 398 } 399 400 final ArrayList<Control> controls = new ArrayList<>(values.length); 401 for (final byte[] controlBytes : values) 402 { 403 try 404 { 405 controls.add(Control.decode(ASN1Sequence.decodeAsSequence( 406 controlBytes))); 407 } 408 catch (final Exception e) 409 { 410 Debug.debugException(e); 411 throw new LDAPException(ResultCode.DECODING_ERROR, 412 ERR_LOGSCHEMA_DECODE_CONTROL_ERROR.get(entry.getDN(), 413 attributeName, StaticUtils.getExceptionMessage(e)), 414 e); 415 } 416 } 417 418 return Collections.unmodifiableList(controls); 419 } 420 421 422 423 /** 424 * Retrieves the type of operation represented by this access log entry. 425 * 426 * @return The type of operation represented by this access log entry. 427 */ 428 @NotNull() 429 public final OperationType getOperationType() 430 { 431 return operationType; 432 } 433 434 435 436 /** 437 * Retrieves the DN of the entry targeted by by the operation represented by 438 * this access log entry, if available. Some types of operations, like 439 * abandon and extended operations, will not have a target entry DN. For a 440 * search operation, this will be the base DN for the search request. For a 441 * modify DN operation, this will be the DN of the entry before any processing 442 * was performed. 443 * 444 * @return The DN of the entry targeted by the operation represented by this 445 * access log entry, or {@code null} if no DN is available. 446 */ 447 @Nullable() 448 public final String getTargetEntryDN() 449 { 450 return targetEntryDN; 451 } 452 453 454 455 /** 456 * Retrieves the string representation of the time that the server started 457 * processing the operation represented by this access log entry. Note that 458 * the string representation of this start time may have a different precision 459 * than the parsed start time returned by the 460 * {@link #getProcessingStartTimeDate()} method. 461 * 462 * @return The string representation of the time that the server started 463 * processing the operation represented by this access log entry. 464 */ 465 @NotNull() 466 public final String getProcessingStartTimeString() 467 { 468 return processingStartTimeString; 469 } 470 471 472 473 /** 474 * Retrieves a parsed representation of the time that the server started 475 * processing the operation represented by this access log entry. Note that 476 * this parsed representation may have a different precision than the start 477 * time string returned by the {@link #getProcessingStartTimeString()} method. 478 * 479 * @return A parsed representation of the time that the server started 480 * processing the operation represented by this access log entry. 481 */ 482 @NotNull() 483 public final Date getProcessingStartTimeDate() 484 { 485 return processingStartTimeDate; 486 } 487 488 489 490 /** 491 * Retrieves the string representation of the time that the server completed 492 * processing the operation represented by this access log entry, if 493 * available. Note that the string representation of this end time may have a 494 * different precision than the parsed end time returned by the 495 * {@link #getProcessingEndTimeDate()} method. 496 * 497 * @return The string representation of the time that the server completed 498 * processing the operation represented by this access log entry, or 499 * {@code null} if no end time is available. 500 */ 501 @Nullable() 502 public final String getProcessingEndTimeString() 503 { 504 return processingEndTimeString; 505 } 506 507 508 509 /** 510 * Retrieves a parsed representation of the time that the server completed 511 * processing the operation represented by this access log entry, if 512 * available. Note that this parsed representation may have a different 513 * precision than the end time string returned by the 514 * {@link #getProcessingEndTimeString()} method. 515 * 516 * @return A parsed representation of the time that the server completed 517 * processing the operation represented by this access log entry. 518 */ 519 @Nullable() 520 public final Date getProcessingEndTimeDate() 521 { 522 return processingEndTimeDate; 523 } 524 525 526 527 /** 528 * Retrieves the session identifier that the server assigned to the operation 529 * represented by this access log entry and can be used to correlate that 530 * operation with other operations requested on the same client connection. 531 * The server will assign a unique session identifier to each client 532 * connection, and all requests received on that connection will share the 533 * same session ID. 534 * 535 * @return The session identifier that the server assigned to the operation 536 * represented by this access log entry. 537 */ 538 @NotNull() 539 public final String getSessionID() 540 { 541 return sessionID; 542 } 543 544 545 546 /** 547 * Retrieves a list of the request controls for the operation represented by 548 * this access log entry, if any. 549 * 550 * @return A list of the request controls for the operation represented by 551 * this access log entry, or an empty list if there were no request 552 * controls included in the access log entry. 553 */ 554 @NotNull() 555 public final List<Control> getRequestControls() 556 { 557 return requestControls; 558 } 559 560 561 562 /** 563 * Retrieves the set of request controls as an array rather than a list. This 564 * is a convenience method for subclasses that need to create LDAP requests 565 * whose constructors need an array of controls rather than a list. 566 * 567 * @return The set of request controls as an array rather than a list. 568 */ 569 @NotNull() 570 final Control[] getRequestControlArray() 571 { 572 return requestControls.toArray(StaticUtils.NO_CONTROLS); 573 } 574 575 576 577 /** 578 * Retrieves the result code for the operation represented by this access log 579 * entry, if any. 580 * 581 * @return The result code for the operation represented by this access log 582 * entry, or {@code null} if no result code was included in the 583 * access log entry. 584 */ 585 @Nullable() 586 public final ResultCode getResultCode() 587 { 588 return resultCode; 589 } 590 591 592 593 /** 594 * Retrieves the diagnostic message for the operation represented by this 595 * access log entry, if any. 596 * 597 * @return The diagnostic message for the operation represented by this 598 * access log entry, or {@code null} if no result code was included 599 * in the access log entry. 600 */ 601 @Nullable() 602 public final String getDiagnosticMessage() 603 { 604 return diagnosticMessage; 605 } 606 607 608 609 /** 610 * Retrieves the list of referral URLs for the operation represented by this 611 * access log entry, if any. 612 * 613 * @return The list of referral URLs for the operation represented by this 614 * access log entry, or an empty list if no referral URLs were 615 * included in the access log entry. 616 */ 617 @NotNull() 618 public final List<String> getReferralURLs() 619 { 620 return referralURLs; 621 } 622 623 624 625 /** 626 * Retrieves a list of the response controls for the operation represented by 627 * this access log entry, if any. 628 * 629 * @return A list of the response controls for the operation represented by 630 * this access log entry, or an empty list if there were no response 631 * controls included in the access log entry. 632 */ 633 @NotNull() 634 public final List<Control> getResponseControls() 635 { 636 return responseControls; 637 } 638 639 640 641 /** 642 * Retrieves the DN of the account that served as the authorization identity 643 * for the operation represented by this access log entry, if any. 644 * 645 * @return The DN of the account that served as the authorization identity 646 * for the operation represented by this access log entry, or 647 * {@code null} if the authorization identity is not available. 648 */ 649 @Nullable() 650 public final String getAuthorizationIdentityDN() 651 { 652 return authorizationIdentityDN; 653 } 654 655 656 657 /** 658 * Retrieves an {@code LDAPResult} object that represents the server response 659 * described by this access log entry, if any. Note that for some types of 660 * operations, like abandon and unbind operations, the server will not return 661 * a result to the client. 662 * 663 * @return An {@code LDAPResult} object that represents the server response 664 * described by this access log entry, or {@code null} if no response 665 * information is available. 666 */ 667 @Nullable() 668 public final LDAPResult toLDAPResult() 669 { 670 if (resultCode == null) 671 { 672 return null; 673 } 674 675 return new LDAPResult(-1, resultCode, diagnosticMessage, null, referralURLs, 676 responseControls); 677 } 678 679 680 681 /** 682 * Decodes the provided entry as an access log entry of the appropriate type. 683 * 684 * @param entry The entry to decode as an access log entry. It must not be 685 * {@code null}. 686 * 687 * @return The decoded access log entry. 688 * 689 * @throws LDAPException If the provided entry cannot be decoded as a valid 690 * access log entry as per the specification contained 691 * in draft-chu-ldap-logschema-00. 692 */ 693 @NotNull() 694 public static DraftChuLDAPLogSchema00Entry decode(@NotNull final Entry entry) 695 throws LDAPException 696 { 697 final String opType = entry.getAttributeValue(ATTR_OPERATION_TYPE); 698 if (opType == null) 699 { 700 throw new LDAPException(ResultCode.DECODING_ERROR, 701 ERR_LOGSCHEMA_DECODE_NO_OP_TYPE.get(entry.getDN(), 702 ATTR_OPERATION_TYPE)); 703 } 704 705 final String lowerOpType = StaticUtils.toLowerCase(opType); 706 if (lowerOpType.equals("abandon")) 707 { 708 return new DraftChuLDAPLogSchema00AbandonEntry(entry); 709 } 710 else if (lowerOpType.equals("add")) 711 { 712 return new DraftChuLDAPLogSchema00AddEntry(entry); 713 } 714 else if (lowerOpType.equals("bind")) 715 { 716 return new DraftChuLDAPLogSchema00BindEntry(entry); 717 } 718 else if (lowerOpType.equals("compare")) 719 { 720 return new DraftChuLDAPLogSchema00CompareEntry(entry); 721 } 722 else if (lowerOpType.equals("delete")) 723 { 724 return new DraftChuLDAPLogSchema00DeleteEntry(entry); 725 } 726 else if (lowerOpType.startsWith("extended")) 727 { 728 return new DraftChuLDAPLogSchema00ExtendedEntry(entry); 729 } 730 else if (lowerOpType.equals("modify")) 731 { 732 return new DraftChuLDAPLogSchema00ModifyEntry(entry); 733 } 734 else if (lowerOpType.equals("modrdn")) 735 { 736 return new DraftChuLDAPLogSchema00ModifyDNEntry(entry); 737 } 738 else if (lowerOpType.equals("search")) 739 { 740 return new DraftChuLDAPLogSchema00SearchEntry(entry); 741 } 742 else if (lowerOpType.equals("unbind")) 743 { 744 return new DraftChuLDAPLogSchema00UnbindEntry(entry); 745 } 746 else 747 { 748 throw new LDAPException(ResultCode.DECODING_ERROR, 749 ERR_LOGSCHEMA_DECODE_UNRECOGNIZED_OP_TYPE.get( 750 entry.getDN(), ATTR_OPERATION_TYPE, opType)); 751 } 752 } 753}