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