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