001/* 002 * Copyright 2020-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2020-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) 2020-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; 037 038 039 040import java.net.InetAddress; 041import java.text.SimpleDateFormat; 042import java.util.ArrayList; 043import java.util.Arrays; 044import java.util.Collections; 045import java.util.Date; 046import java.util.EnumSet; 047import java.util.HashSet; 048import java.util.LinkedHashMap; 049import java.util.LinkedHashSet; 050import java.util.List; 051import java.util.Map; 052import java.util.Set; 053import java.util.logging.Handler; 054import java.util.logging.Level; 055import java.util.logging.LogRecord; 056 057import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition; 058import com.unboundid.ldap.sdk.schema.Schema; 059import com.unboundid.util.Debug; 060import com.unboundid.util.NotMutable; 061import com.unboundid.util.NotNull; 062import com.unboundid.util.Nullable; 063import com.unboundid.util.StaticUtils; 064import com.unboundid.util.ThreadSafety; 065import com.unboundid.util.ThreadSafetyLevel; 066import com.unboundid.util.json.JSONBuffer; 067 068 069 070/** 071 * This class provides an implementation of an LDAP connection access logger 072 * that records messages as JSON objects. 073 */ 074@NotMutable() 075@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 076public final class JSONLDAPConnectionLogger 077 extends LDAPConnectionLogger 078{ 079 /** 080 * The bytes that comprise the value that will be used in place of redacted 081 * attribute values. 082 */ 083 @NotNull private static final String REDACTED_VALUE_STRING = "[REDACTED]"; 084 085 086 087 /** 088 * The bytes that comprise the value that will be used in place of redacted 089 * attribute values. 090 */ 091 @NotNull private static final byte[] REDACTED_VALUE_BYTES = 092 StaticUtils.getBytes(REDACTED_VALUE_STRING); 093 094 095 096 // Indicates whether to flush the handler after logging information about each 097 // successful for failed connection attempt. 098 private final boolean flushAfterConnectMessages; 099 100 // Indicates whether to flush the handler after logging information about each 101 // disconnect. 102 private final boolean flushAfterDisconnectMessages; 103 104 // Indicates whether to flush the handler after logging information about each 105 // request. 106 private final boolean flushAfterRequestMessages; 107 108 // Indicates whether to flush the handler after logging information about the 109 // final result for each operation. 110 private final boolean flushAfterFinalResultMessages; 111 112 // Indicates whether to flush the handler after logging information about each 113 // non-final result (including search result entries, search result 114 // references, and intermediate response messages) for each operation. 115 private final boolean flushAfterNonFinalResultMessages; 116 117 // Indicates whether to include the names of attributes provided in add 118 // requests. 119 private final boolean includeAddAttributeNames; 120 121 // Indicates whether to include the values of attributes provided in add 122 // requests. 123 private final boolean includeAddAttributeValues; 124 125 // Indicates whether to include the names of attributes targeted by modify 126 // requests. 127 private final boolean includeModifyAttributeNames; 128 129 // Indicates whether to include the values of attributes targeted by modify 130 // requests. 131 private final boolean includeModifyAttributeValues; 132 133 // Indicates whether to include the OIDs of controls included in requests and 134 // results. 135 private final boolean includeControlOIDs; 136 137 // Indicates whether to include the names of attributes provided in search 138 // result entries. 139 private final boolean includeSearchEntryAttributeNames; 140 141 // Indicates whether to include the values of attributes provided in search 142 // result entries. 143 private final boolean includeSearchEntryAttributeValues; 144 145 // Indicates whether to log successful and failed connection attempts. 146 private final boolean logConnects; 147 148 // Indicates whether to log disconnects. 149 private final boolean logDisconnects; 150 151 // Indicates whether to log intermediate response messages. 152 private final boolean logIntermediateResponses; 153 154 // Indicates whether to log operation requests for enabled operation types. 155 private final boolean logRequests; 156 157 // Indicates whether to log final operation results for enabled operation 158 // types. 159 private final boolean logFinalResults; 160 161 // Indicates whether to log search result entries. 162 private final boolean logSearchEntries; 163 164 // Indicates whether to log search result references. 165 private final boolean logSearchReferences; 166 167 // The log handler that will be used to actually log the messages. 168 @NotNull private final Handler logHandler; 169 170 // The schema to use for identifying alternate attribute type names. 171 @Nullable private final Schema schema; 172 173 // The types of operations for which requests should be logged. 174 @NotNull private final Set<OperationType> operationTypes; 175 176 // The names or OIDs of the attributes whose values should be redacted. 177 @NotNull private final Set<String> attributesToRedact; 178 179 // The full set of the names and OIDs for attributes whose values should be 180 // redacted. 181 @NotNull private final Set<String> fullAttributesToRedact; 182 183 // The set of thread-local JSON buffers that will be used for formatting log 184 // messages. 185 @NotNull private final ThreadLocal<JSONBuffer> jsonBuffers; 186 187 // The set of thread-local date formatters that will be used for formatting 188 // timestamps. 189 @NotNull private final ThreadLocal<SimpleDateFormat> timestampFormatters; 190 191 192 193 /** 194 * Creates a new instance of this LDAP connection logger that will write 195 * messages to the provided log handler using the given set of properties. 196 * 197 * @param logHandler The log handler that will be used to actually log the 198 * messages. All messages will be logged with a level of 199 * {@code INFO}. 200 * @param properties The properties to use for this logger. 201 */ 202 public JSONLDAPConnectionLogger(@NotNull final Handler logHandler, 203 @NotNull final JSONLDAPConnectionLoggerProperties properties) 204 { 205 this.logHandler = logHandler; 206 207 flushAfterConnectMessages = properties.flushAfterConnectMessages(); 208 flushAfterDisconnectMessages = properties.flushAfterDisconnectMessages(); 209 flushAfterRequestMessages = properties.flushAfterRequestMessages(); 210 flushAfterFinalResultMessages = 211 properties.flushAfterFinalResultMessages(); 212 flushAfterNonFinalResultMessages = 213 properties.flushAfterNonFinalResultMessages(); 214 includeAddAttributeNames = properties.includeAddAttributeNames(); 215 includeAddAttributeValues = properties.includeAddAttributeValues(); 216 includeModifyAttributeNames = properties.includeModifyAttributeNames(); 217 includeModifyAttributeValues = properties.includeModifyAttributeValues(); 218 includeControlOIDs = properties.includeControlOIDs(); 219 includeSearchEntryAttributeNames = 220 properties.includeSearchEntryAttributeNames(); 221 includeSearchEntryAttributeValues = 222 properties.includeSearchEntryAttributeValues(); 223 logConnects = properties.logConnects(); 224 logDisconnects = properties.logDisconnects(); 225 logIntermediateResponses = properties.logIntermediateResponses(); 226 logRequests = properties.logRequests(); 227 logFinalResults = properties.logFinalResults(); 228 logSearchEntries = properties.logSearchEntries(); 229 logSearchReferences = properties.logSearchReferences(); 230 schema = properties.getSchema(); 231 232 attributesToRedact = Collections.unmodifiableSet(new LinkedHashSet<>( 233 properties.getAttributesToRedact())); 234 235 final EnumSet<OperationType> opTypes = EnumSet.noneOf(OperationType.class); 236 opTypes.addAll(properties.getOperationTypes()); 237 operationTypes = Collections.unmodifiableSet(opTypes); 238 239 jsonBuffers = new ThreadLocal<>(); 240 timestampFormatters = new ThreadLocal<>(); 241 242 final Set<String> fullAttrsToRedact = new HashSet<>(); 243 for (final String attr : attributesToRedact) 244 { 245 fullAttrsToRedact.add(StaticUtils.toLowerCase(attr)); 246 247 if (schema != null) 248 { 249 final AttributeTypeDefinition d = schema.getAttributeType(attr); 250 if (d != null) 251 { 252 fullAttrsToRedact.add(StaticUtils.toLowerCase(d.getOID())); 253 for (final String name : d.getNames()) 254 { 255 fullAttrsToRedact.add(StaticUtils.toLowerCase(name)); 256 } 257 } 258 } 259 } 260 261 fullAttributesToRedact = Collections.unmodifiableSet(fullAttrsToRedact); 262 } 263 264 265 266 /** 267 * Indicates whether to log successful and failed connection attempts. 268 * Connection attempts will be logged by default. 269 * 270 * @return {@code true} if connection attempts should be logged, or 271 * {@code false} if not. 272 */ 273 public boolean logConnects() 274 { 275 return logConnects; 276 } 277 278 279 280 /** 281 * Indicates whether to log disconnects. Disconnects will be logged by 282 * default. 283 * 284 * @return {@code true} if disconnects should be logged, or {@code false} if 285 * not. 286 */ 287 public boolean logDisconnects() 288 { 289 return logDisconnects; 290 } 291 292 293 294 /** 295 * Indicates whether to log messages about requests for operations included 296 * in the set of operation types returned by the {@link #getOperationTypes} 297 * method. Operation requests will be logged by default. 298 * 299 * @return {@code true} if operation requests should be logged for 300 * appropriate operation types, or {@code false} if not. 301 */ 302 public boolean logRequests() 303 { 304 return logRequests; 305 } 306 307 308 309 /** 310 * Indicates whether to log messages about the final reults for operations 311 * included in the set of operation types returned by the 312 * {@link #getOperationTypes} method. Final operation results will be 313 * logged by default. 314 * 315 * @return {@code true} if operation requests should be logged for 316 * appropriate operation types, or {@code false} if not. 317 */ 318 public boolean logFinalResults() 319 { 320 return logFinalResults; 321 } 322 323 324 325 /** 326 * Indicates whether to log messages about each search result entry returned 327 * for search operations. This property will only be used if the set returned 328 * by the {@link #getOperationTypes} method includes 329 * {@link OperationType#SEARCH}. Search result entries will not be logged by 330 * default. 331 * 332 * @return {@code true} if search result entries should be logged, or 333 * {@code false} if not. 334 */ 335 public boolean logSearchEntries() 336 { 337 return logSearchEntries; 338 } 339 340 341 342 /** 343 * Indicates whether to log messages about each search result reference 344 * returned for search operations. This property will only be used if the set 345 * returned by the {@link #getOperationTypes} method includes 346 * {@link OperationType#SEARCH}. Search result references will not be logged 347 * by default. 348 * 349 * @return {@code true} if search result references should be logged, or 350 * {@code false} if not. 351 */ 352 public boolean logSearchReferences() 353 { 354 return logSearchReferences; 355 } 356 357 358 359 /** 360 * Indicates whether to log messages about each intermediate response returned 361 * in the course of processing an operation. Intermediate response messages 362 * will be logged by default. 363 * 364 * @return {@code true} if intermediate response messages should be logged, 365 * or {@code false} if not. 366 */ 367 public boolean logIntermediateResponses() 368 { 369 return logIntermediateResponses; 370 } 371 372 373 374 /** 375 * Retrieves the set of operation types for which to log requests and 376 * results. All operation types will be logged by default. 377 * 378 * @return The set of operation types for which to log requests and results. 379 */ 380 @NotNull() 381 public Set<OperationType> getOperationTypes() 382 { 383 return operationTypes; 384 } 385 386 387 388 /** 389 * Indicates whether log messages about add requests should include the names 390 * of the attributes provided in the request. Add attribute names (but not 391 * values) will be logged by default. 392 * 393 * @return {@code true} if add attribute names should be logged, or 394 * {@code false} if not. 395 */ 396 public boolean includeAddAttributeNames() 397 { 398 return includeAddAttributeNames; 399 } 400 401 402 403 /** 404 * Indicates whether log messages about add requests should include the values 405 * of the attributes provided in the request. This property will only be used 406 * if {@link #includeAddAttributeNames} returns {@code true}. Values for 407 * attributes named in the set returned by the 408 * {@link #getAttributesToRedact} method will be replaced with a value of 409 * "[REDACTED]". Add attribute names (but not values) will be 410 * logged by default. 411 * 412 * @return {@code true} if add attribute values should be logged, or 413 * {@code false} if not. 414 */ 415 public boolean includeAddAttributeValues() 416 { 417 return includeAddAttributeValues; 418 } 419 420 421 422 /** 423 * Indicates whether log messages about modify requests should include the 424 * names of the attributes modified in the request. Modified attribute names 425 * (but not values) will be logged by default. 426 * 427 * @return {@code true} if modify attribute names should be logged, or 428 * {@code false} if not. 429 */ 430 public boolean includeModifyAttributeNames() 431 { 432 return includeModifyAttributeNames; 433 } 434 435 436 437 /** 438 * Indicates whether log messages about modify requests should include the 439 * values of the attributes modified in the request. This property will only 440 * be used if {@link #includeModifyAttributeNames} returns {@code true}. 441 * Values for attributes named in the set returned by the 442 * {@link #getAttributesToRedact} method will be replaced with a value of 443 * "[REDACTED]". Modify attribute names (but not values) will be 444 * logged by default. 445 * 446 * @return {@code true} if modify attribute values should be logged, or 447 * {@code false} if not. 448 */ 449 public boolean includeModifyAttributeValues() 450 { 451 return includeModifyAttributeValues; 452 } 453 454 455 456 /** 457 * Indicates whether log messages about search result entries should include 458 * the names of the attributes in the returned entry. Entry attribute names 459 * (but not values) will be logged by default. 460 * 461 * @return {@code true} if search result entry attribute names should be 462 * logged, or {@code false} if not. 463 */ 464 public boolean includeSearchEntryAttributeNames() 465 { 466 return includeSearchEntryAttributeNames; 467 } 468 469 470 471 /** 472 * Indicates whether log messages about search result entries should include 473 * the values of the attributes in the returned entry. This property will 474 * only be used if {@link #includeSearchEntryAttributeNames} returns 475 * {@code true}. Values for attributes named in the set returned by the 476 * {@link #getAttributesToRedact} method will be replaced with a value of 477 * "[REDACTED]". Entry attribute names (but not values) will be 478 * logged by default. 479 * 480 * @return {@code true} if search result entry attribute values should be 481 * logged, or {@code false} if not. 482 */ 483 public boolean includeSearchEntryAttributeValues() 484 { 485 return includeSearchEntryAttributeValues; 486 } 487 488 489 490 /** 491 * Retrieves a set containing the names or OIDs of the attributes whose values 492 * should be redacted from log messages. Values of the userPassword, 493 * authPassword, and unicodePWD attributes will be redacted by default. 494 * 495 * @return A set containing the names or OIDs of the attributes whose values 496 * should be redacted from log messages, or an empty set if no 497 * attribute values should be redacted. 498 */ 499 @NotNull() 500 public Set<String> getAttributesToRedact() 501 { 502 return attributesToRedact; 503 } 504 505 506 507 /** 508 * Indicates whether request and result log messages should include the OIDs 509 * of any controls included in that request or result. Control OIDs will 510 * be logged by default. 511 * 512 * @return {@code true} if request control OIDs should be logged, or 513 * {@code false} if not. 514 */ 515 public boolean includeControlOIDs() 516 { 517 return includeControlOIDs; 518 } 519 520 521 522 /** 523 * Indicates whether the log handler should be flushed after logging each 524 * successful or failed connection attempt. By default, the handler will be 525 * flushed after logging each connection attempt. 526 * 527 * @return {@code true} if the log handler should be flushed after logging 528 * each connection attempt, or {@code false} if not. 529 */ 530 public boolean flushAfterConnectMessages() 531 { 532 return flushAfterConnectMessages; 533 } 534 535 536 537 /** 538 * Indicates whether the log handler should be flushed after logging each 539 * disconnect. By default, the handler will be flushed after logging each 540 * disconnect. 541 * 542 * @return {@code true} if the log handler should be flushed after logging 543 * each disconnect, or {@code false} if not. 544 */ 545 public boolean flushAfterDisconnectMessages() 546 { 547 return flushAfterDisconnectMessages; 548 } 549 550 551 552 /** 553 * Indicates whether the log handler should be flushed after logging each 554 * request. By default, the handler will be flushed after logging each final 555 * result, but not after logging requests or non-final results. 556 * 557 * @return {@code true} if the log handler should be flushed after logging 558 * each request, or {@code false} if not. 559 */ 560 public boolean flushAfterRequestMessages() 561 { 562 return flushAfterRequestMessages; 563 } 564 565 566 567 /** 568 * Indicates whether the log handler should be flushed after logging each 569 * non-final result (including search result entries, search result 570 * references, and intermediate response messages). By default, the handler 571 * will be flushed after logging each final result, but not after logging 572 * requests or non-final results. 573 * 574 * @return {@code true} if the log handler should be flushed after logging 575 * each non-final result, or {@code false} if not. 576 */ 577 public boolean flushAfterNonFinalResultMessages() 578 { 579 return flushAfterNonFinalResultMessages; 580 } 581 582 583 584 /** 585 * Indicates whether the log handler should be flushed after logging the final 586 * result for each operation. By default, the handler will be flushed after 587 * logging each final result, but not after logging requests or non-final 588 * results. 589 * 590 * @return {@code true} if the log handler should be flushed after logging 591 * each final result, or {@code false} if not. 592 */ 593 public boolean flushAfterFinalResultMessages() 594 { 595 return flushAfterFinalResultMessages; 596 } 597 598 599 600 /** 601 * Retrieves the schema that will be used to identify alternate names and OIDs 602 * for attributes whose values should be redacted. The LDAP SDK's default 603 * standard schema will be used by default. 604 * 605 * @return The schema that will be used to identify alternate names and OIDs 606 * for attributes whose values should be redacted, or {@code null} 607 * if no schema should be used. 608 */ 609 @Nullable() 610 public Schema getSchema() 611 { 612 return schema; 613 } 614 615 616 617 /** 618 * {@inheritDoc} 619 */ 620 @Override() 621 public void logConnect(@NotNull final LDAPConnectionInfo connectionInfo, 622 @NotNull final String host, 623 @NotNull final InetAddress inetAddress, 624 final int port) 625 { 626 if (logConnects) 627 { 628 final JSONBuffer buffer = startLogMessage("connect", null, 629 connectionInfo, -1); 630 631 buffer.appendString("hostname", host); 632 buffer.appendString("ip-address", inetAddress.getHostAddress()); 633 buffer.appendNumber("port", port); 634 635 logMessage(buffer, flushAfterConnectMessages); 636 } 637 } 638 639 640 641 /** 642 * {@inheritDoc} 643 */ 644 @Override() 645 public void logConnectFailure( 646 @NotNull final LDAPConnectionInfo connectionInfo, 647 @NotNull final String host, final int port, 648 @NotNull final LDAPException connectException) 649 { 650 if (logConnects) 651 { 652 final JSONBuffer buffer = startLogMessage("connect-failure", null, 653 connectionInfo, -1); 654 655 buffer.appendString("hostname", host); 656 buffer.appendNumber("port", port); 657 658 if (connectException != null) 659 { 660 appendException(buffer, "connect-exception", connectException); 661 } 662 663 logMessage(buffer, flushAfterConnectMessages); 664 } 665 } 666 667 668 669 /** 670 * {@inheritDoc} 671 */ 672 @Override() 673 public void logDisconnect( 674 @NotNull final LDAPConnectionInfo connectionInfo, 675 @NotNull final String host, final int port, 676 @NotNull final DisconnectType disconnectType, 677 @Nullable final String disconnectMessage, 678 @Nullable final Throwable disconnectCause) 679 { 680 if (logDisconnects) 681 { 682 final JSONBuffer buffer = startLogMessage("disconnect", null, 683 connectionInfo, -1); 684 685 buffer.appendString("hostname", host); 686 buffer.appendNumber("port", port); 687 buffer.appendString("disconnect-type", disconnectType.name()); 688 689 if (disconnectMessage != null) 690 { 691 buffer.appendString("disconnect-message", disconnectMessage); 692 } 693 694 if (disconnectCause != null) 695 { 696 appendException(buffer, "disconnect-cause", disconnectCause); 697 } 698 699 logMessage(buffer, flushAfterDisconnectMessages); 700 } 701 } 702 703 704 705 /** 706 * {@inheritDoc} 707 */ 708 @Override() 709 public void logAbandonRequest( 710 @NotNull final LDAPConnectionInfo connectionInfo, 711 final int messageID, 712 final int messageIDToAbandon, 713 @NotNull final List<Control> requestControls) 714 { 715 if (logRequests && operationTypes.contains(OperationType.ABANDON)) 716 { 717 final JSONBuffer buffer = startLogMessage("request", 718 OperationType.ABANDON, connectionInfo, messageID); 719 720 buffer.appendNumber("message-id-to-abandon", messageIDToAbandon); 721 appendControls(buffer, "control-oids", requestControls); 722 723 logMessage(buffer, flushAfterRequestMessages); 724 } 725 } 726 727 728 729 /** 730 * {@inheritDoc} 731 */ 732 @Override() 733 public void logAddRequest(@NotNull final LDAPConnectionInfo connectionInfo, 734 final int messageID, 735 @NotNull final ReadOnlyAddRequest addRequest) 736 { 737 if (logRequests && operationTypes.contains(OperationType.ADD)) 738 { 739 final JSONBuffer buffer = startLogMessage("request", 740 OperationType.ADD, connectionInfo, messageID); 741 742 appendDN(buffer, "dn", addRequest.getDN()); 743 744 if (includeAddAttributeNames) 745 { 746 appendAttributes(buffer, "attributes", addRequest.getAttributes(), 747 includeAddAttributeValues); 748 } 749 750 appendControls(buffer, "control-oids", addRequest.getControls()); 751 752 logMessage(buffer, flushAfterRequestMessages); 753 } 754 } 755 756 757 758 /** 759 * {@inheritDoc} 760 */ 761 @Override() 762 public void logAddResult(@NotNull final LDAPConnectionInfo connectionInfo, 763 final int requestMessageID, 764 @NotNull final LDAPResult addResult) 765 { 766 logLDAPResult(connectionInfo, OperationType.ADD, requestMessageID, 767 addResult); 768 } 769 770 771 772 /** 773 * {@inheritDoc} 774 */ 775 @Override() 776 public void logBindRequest(@NotNull final LDAPConnectionInfo connectionInfo, 777 final int messageID, 778 @NotNull final SimpleBindRequest bindRequest) 779 { 780 if (logRequests && operationTypes.contains(OperationType.BIND)) 781 { 782 final JSONBuffer buffer = startLogMessage("request", 783 OperationType.BIND, connectionInfo, messageID); 784 785 buffer.appendString("authentication-type", "simple"); 786 appendDN(buffer, "dn", bindRequest.getBindDN()); 787 788 appendControls(buffer, "control-oids", bindRequest.getControls()); 789 790 logMessage(buffer, flushAfterRequestMessages); 791 } 792 } 793 794 795 796 /** 797 * {@inheritDoc} 798 */ 799 @Override() 800 public void logBindRequest(@NotNull final LDAPConnectionInfo connectionInfo, 801 final int messageID, 802 @NotNull final SASLBindRequest bindRequest) 803 { 804 if (logRequests && operationTypes.contains(OperationType.BIND)) 805 { 806 final JSONBuffer buffer = startLogMessage("request", 807 OperationType.BIND, connectionInfo, messageID); 808 809 buffer.appendString("authentication-type", "SASL"); 810 buffer.appendString("sasl-mechanism", bindRequest.getSASLMechanismName()); 811 812 appendControls(buffer, "control-oids", bindRequest.getControls()); 813 814 logMessage(buffer, flushAfterRequestMessages); 815 } 816 } 817 818 819 820 /** 821 * {@inheritDoc} 822 */ 823 @Override() 824 public void logBindResult(@NotNull final LDAPConnectionInfo connectionInfo, 825 final int requestMessageID, 826 @NotNull final BindResult bindResult) 827 { 828 logLDAPResult(connectionInfo, OperationType.BIND, requestMessageID, 829 bindResult); 830 } 831 832 833 834 /** 835 * {@inheritDoc} 836 */ 837 @Override() 838 public void logCompareRequest( 839 @NotNull final LDAPConnectionInfo connectionInfo, 840 final int messageID, 841 @NotNull final ReadOnlyCompareRequest compareRequest) 842 { 843 if (logRequests && operationTypes.contains(OperationType.COMPARE)) 844 { 845 final JSONBuffer buffer = startLogMessage("request", 846 OperationType.COMPARE, connectionInfo, messageID); 847 848 appendDN(buffer, "dn", compareRequest.getDN()); 849 appendDN(buffer, "attribute-type", compareRequest.getAttributeName()); 850 851 final String baseName = StaticUtils.toLowerCase( 852 Attribute.getBaseName(compareRequest.getAttributeName())); 853 if (fullAttributesToRedact.contains(baseName)) 854 { 855 buffer.appendString("assertion-value", REDACTED_VALUE_STRING); 856 } 857 else 858 { 859 buffer.appendString("assertion-value", 860 compareRequest.getAssertionValue()); 861 } 862 863 appendControls(buffer, "control-oids", compareRequest.getControls()); 864 865 logMessage(buffer, flushAfterRequestMessages); 866 } 867 } 868 869 870 871 /** 872 * {@inheritDoc} 873 */ 874 @Override() 875 public void logCompareResult(@NotNull final LDAPConnectionInfo connectionInfo, 876 final int requestMessageID, 877 @NotNull final LDAPResult compareResult) 878 { 879 logLDAPResult(connectionInfo, OperationType.COMPARE, requestMessageID, 880 compareResult); 881 } 882 883 884 885 /** 886 * {@inheritDoc} 887 */ 888 @Override() 889 public void logDeleteRequest(@NotNull final LDAPConnectionInfo connectionInfo, 890 final int messageID, 891 @NotNull final ReadOnlyDeleteRequest deleteRequest) 892 { 893 if (logRequests && operationTypes.contains(OperationType.DELETE)) 894 { 895 final JSONBuffer buffer = startLogMessage("request", 896 OperationType.DELETE, connectionInfo, messageID); 897 898 appendDN(buffer, "dn", deleteRequest.getDN()); 899 appendControls(buffer, "control-oids", deleteRequest.getControls()); 900 901 logMessage(buffer, flushAfterRequestMessages); 902 } 903 } 904 905 906 907 /** 908 * {@inheritDoc} 909 */ 910 @Override() 911 public void logDeleteResult(@NotNull final LDAPConnectionInfo connectionInfo, 912 final int requestMessageID, 913 @NotNull final LDAPResult deleteResult) 914 { 915 logLDAPResult(connectionInfo, OperationType.DELETE, requestMessageID, 916 deleteResult); 917 } 918 919 920 921 /** 922 * {@inheritDoc} 923 */ 924 @Override() 925 public void logExtendedRequest( 926 @NotNull final LDAPConnectionInfo connectionInfo, 927 final int messageID, 928 @NotNull final ExtendedRequest extendedRequest) 929 { 930 if (logRequests && operationTypes.contains(OperationType.EXTENDED)) 931 { 932 final JSONBuffer buffer = startLogMessage("request", 933 OperationType.EXTENDED, connectionInfo, messageID); 934 935 buffer.appendString("oid", extendedRequest.getOID()); 936 buffer.appendBoolean("has-value", (extendedRequest.getValue() != null)); 937 938 appendControls(buffer, "control-oids", extendedRequest.getControls()); 939 940 logMessage(buffer, flushAfterRequestMessages); 941 } 942 } 943 944 945 946 /** 947 * {@inheritDoc} 948 */ 949 @Override() 950 public void logExtendedResult( 951 @NotNull final LDAPConnectionInfo connectionInfo, 952 final int requestMessageID, 953 @NotNull final ExtendedResult extendedResult) 954 { 955 logLDAPResult(connectionInfo, OperationType.EXTENDED, requestMessageID, 956 extendedResult); 957 } 958 959 960 961 /** 962 * {@inheritDoc} 963 */ 964 @Override() 965 public void logModifyRequest(@NotNull final LDAPConnectionInfo connectionInfo, 966 final int messageID, 967 @NotNull final ReadOnlyModifyRequest modifyRequest) 968 { 969 if (logRequests && operationTypes.contains(OperationType.MODIFY)) 970 { 971 final JSONBuffer buffer = startLogMessage("request", 972 OperationType.MODIFY, connectionInfo, messageID); 973 974 appendDN(buffer, "dn", modifyRequest.getDN()); 975 976 if (includeModifyAttributeNames) 977 { 978 final List<Modification> mods = modifyRequest.getModifications(); 979 980 if (includeModifyAttributeValues) 981 { 982 buffer.beginArray("modifications"); 983 for (final Modification m : mods) 984 { 985 buffer.beginObject(); 986 987 final String name = m.getAttributeName(); 988 buffer.appendString("attribute-name", name); 989 buffer.appendString("modification-type", 990 m.getModificationType().getName()); 991 992 buffer.beginArray("attribute-values"); 993 final String baseName = 994 StaticUtils.toLowerCase(Attribute.getBaseName(name)); 995 if (fullAttributesToRedact.contains(baseName)) 996 { 997 for (final String value : m.getValues()) 998 { 999 buffer.appendString(REDACTED_VALUE_STRING); 1000 } 1001 } 1002 else 1003 { 1004 for (final String value : m.getValues()) 1005 { 1006 buffer.appendString(value); 1007 } 1008 } 1009 1010 buffer.endArray(); 1011 buffer.endObject(); 1012 } 1013 1014 buffer.endArray(); 1015 } 1016 else 1017 { 1018 final Map<String,String> modifiedAttributes = new LinkedHashMap<>( 1019 StaticUtils.computeMapCapacity(mods.size())); 1020 for (final Modification m : modifyRequest.getModifications()) 1021 { 1022 final String name = m.getAttributeName(); 1023 final String lowerName = StaticUtils.toLowerCase(name); 1024 if (! modifiedAttributes.containsKey(lowerName)) 1025 { 1026 modifiedAttributes.put(lowerName, name); 1027 } 1028 } 1029 1030 buffer.beginArray("modified-attributes"); 1031 for (final String attributeName : modifiedAttributes.values()) 1032 { 1033 buffer.appendString(attributeName); 1034 } 1035 1036 buffer.endArray(); 1037 } 1038 } 1039 1040 appendControls(buffer, "control-oids", modifyRequest.getControls()); 1041 1042 logMessage(buffer, flushAfterRequestMessages); 1043 } 1044 } 1045 1046 1047 1048 /** 1049 * {@inheritDoc} 1050 */ 1051 @Override() 1052 public void logModifyResult(@NotNull final LDAPConnectionInfo connectionInfo, 1053 final int requestMessageID, 1054 @NotNull final LDAPResult modifyResult) 1055 { 1056 logLDAPResult(connectionInfo, OperationType.MODIFY, requestMessageID, 1057 modifyResult); 1058 } 1059 1060 1061 1062 /** 1063 * {@inheritDoc} 1064 */ 1065 @Override() 1066 public void logModifyDNRequest( 1067 @NotNull final LDAPConnectionInfo connectionInfo, 1068 final int messageID, 1069 @NotNull final ReadOnlyModifyDNRequest modifyDNRequest) 1070 { 1071 if (logRequests && operationTypes.contains(OperationType.MODIFY_DN)) 1072 { 1073 final JSONBuffer buffer = startLogMessage("request", 1074 OperationType.MODIFY_DN, connectionInfo, messageID); 1075 1076 appendDN(buffer, "dn", modifyDNRequest.getDN()); 1077 appendDN(buffer, "new-rdn", modifyDNRequest.getNewRDN()); 1078 buffer.appendBoolean("delete-old-rdn", modifyDNRequest.deleteOldRDN()); 1079 1080 final String newSuperiorDN = modifyDNRequest.getNewSuperiorDN(); 1081 if (newSuperiorDN != null) 1082 { 1083 appendDN(buffer, "new-superior-dn", newSuperiorDN); 1084 } 1085 1086 appendControls(buffer, "control-oids", modifyDNRequest.getControls()); 1087 1088 logMessage(buffer, flushAfterRequestMessages); 1089 } 1090 } 1091 1092 1093 1094 /** 1095 * {@inheritDoc} 1096 */ 1097 @Override() 1098 public void logModifyDNResult( 1099 @NotNull final LDAPConnectionInfo connectionInfo, 1100 final int requestMessageID, 1101 @NotNull final LDAPResult modifyDNResult) 1102 { 1103 logLDAPResult(connectionInfo, OperationType.MODIFY_DN, requestMessageID, 1104 modifyDNResult); 1105 } 1106 1107 1108 1109 /** 1110 * {@inheritDoc} 1111 */ 1112 @Override() 1113 public void logSearchRequest(@NotNull final LDAPConnectionInfo connectionInfo, 1114 final int messageID, 1115 @NotNull final ReadOnlySearchRequest searchRequest) 1116 { 1117 if (logRequests && operationTypes.contains(OperationType.SEARCH)) 1118 { 1119 final JSONBuffer buffer = startLogMessage("request", 1120 OperationType.SEARCH, connectionInfo, messageID); 1121 1122 appendDN(buffer, "base-dn", searchRequest.getBaseDN()); 1123 1124 buffer.appendString("scope", searchRequest.getScope().getName()); 1125 buffer.appendString("dereference-policy", 1126 searchRequest.getDereferencePolicy().getName()); 1127 buffer.appendNumber("size-limit", searchRequest.getSizeLimit()); 1128 buffer.appendNumber("time-limit-seconds", 1129 searchRequest.getTimeLimitSeconds()); 1130 buffer.appendBoolean("types-only", searchRequest.typesOnly()); 1131 buffer.appendString("filter", 1132 redactFilter(searchRequest.getFilter()).toString()); 1133 1134 buffer.beginArray("requested-attributes"); 1135 for (final String attributeName : searchRequest.getAttributeList()) 1136 { 1137 buffer.appendString(attributeName); 1138 } 1139 buffer.endArray(); 1140 1141 appendControls(buffer, "control-oids", searchRequest.getControls()); 1142 1143 logMessage(buffer, flushAfterRequestMessages); 1144 } 1145 } 1146 1147 1148 1149 /** 1150 * {@inheritDoc} 1151 */ 1152 @Override() 1153 public void logSearchEntry(@NotNull final LDAPConnectionInfo connectionInfo, 1154 final int requestMessageID, 1155 @NotNull final SearchResultEntry searchEntry) 1156 { 1157 if (logSearchEntries && operationTypes.contains(OperationType.SEARCH)) 1158 { 1159 final JSONBuffer buffer = startLogMessage("search-entry", 1160 OperationType.SEARCH, connectionInfo, requestMessageID); 1161 1162 appendDN(buffer, "dn", searchEntry.getDN()); 1163 1164 if (includeSearchEntryAttributeNames) 1165 { 1166 appendAttributes(buffer, "attributes", 1167 new ArrayList<>(searchEntry.getAttributes()), 1168 includeSearchEntryAttributeValues); 1169 } 1170 1171 appendControls(buffer, "control-oids", searchEntry.getControls()); 1172 1173 logMessage(buffer, flushAfterRequestMessages); 1174 } 1175 } 1176 1177 1178 1179 /** 1180 * {@inheritDoc} 1181 */ 1182 @Override() 1183 public void logSearchReference( 1184 @NotNull final LDAPConnectionInfo connectionInfo, 1185 final int requestMessageID, 1186 @NotNull final SearchResultReference searchReference) 1187 { 1188 if (logSearchReferences && operationTypes.contains(OperationType.SEARCH)) 1189 { 1190 final JSONBuffer buffer = startLogMessage("search-reference", 1191 OperationType.SEARCH, connectionInfo, requestMessageID); 1192 1193 buffer.beginArray("referral-urls"); 1194 for (final String url : searchReference.getReferralURLs()) 1195 { 1196 buffer.appendString(url); 1197 } 1198 buffer.endArray(); 1199 1200 appendControls(buffer, "control-oids", searchReference.getControls()); 1201 1202 logMessage(buffer, flushAfterRequestMessages); 1203 } 1204 } 1205 1206 1207 1208 /** 1209 * {@inheritDoc} 1210 */ 1211 @Override() 1212 public void logSearchResult(@NotNull final LDAPConnectionInfo connectionInfo, 1213 final int requestMessageID, 1214 @NotNull final SearchResult searchResult) 1215 { 1216 logLDAPResult(connectionInfo, OperationType.SEARCH, requestMessageID, 1217 searchResult); 1218 } 1219 1220 1221 1222 /** 1223 * {@inheritDoc} 1224 */ 1225 @Override() 1226 public void logUnbindRequest(@NotNull final LDAPConnectionInfo connectionInfo, 1227 final int messageID, 1228 @NotNull final List<Control> requestControls) 1229 { 1230 if (logRequests && operationTypes.contains(OperationType.UNBIND)) 1231 { 1232 final JSONBuffer buffer = startLogMessage("request", 1233 OperationType.UNBIND, connectionInfo, messageID); 1234 1235 appendControls(buffer, "control-oids", requestControls); 1236 1237 logMessage(buffer, flushAfterRequestMessages); 1238 } 1239 } 1240 1241 1242 1243 /** 1244 * {@inheritDoc} 1245 */ 1246 @Override() 1247 public void logIntermediateResponse( 1248 @NotNull final LDAPConnectionInfo connectionInfo, 1249 final int messageID, 1250 @NotNull final IntermediateResponse intermediateResponse) 1251 { 1252 if (logIntermediateResponses) 1253 { 1254 final JSONBuffer buffer = startLogMessage("intermediate-response", null, 1255 connectionInfo, messageID); 1256 1257 final String oid = intermediateResponse.getOID(); 1258 if (oid != null) 1259 { 1260 buffer.appendString("oid", oid); 1261 } 1262 1263 buffer.appendBoolean("has-value", 1264 (intermediateResponse.getValue() != null)); 1265 1266 appendControls(buffer, "control-oids", 1267 intermediateResponse.getControls()); 1268 1269 logMessage(buffer, flushAfterRequestMessages); 1270 } 1271 } 1272 1273 1274 1275 /** 1276 * Starts generating a log message. 1277 * 1278 * @param messageType The message type for the log message. It must not 1279 * be {@code null}. 1280 * @param operationType The operation type for the log message. It may be 1281 * {@code null} if there is no associated operation 1282 * type. 1283 * @param connectionInfo Information about the connection with which the 1284 * message is associated. It must not be 1285 * {@code null}. 1286 * @param messageID The LDAP message ID for the associated operation. 1287 * This will be ignored if the value is less than 1288 * zero. 1289 * 1290 * @return A JSON buffer that may be used to construct the remainder of the 1291 * log message. 1292 */ 1293 @NotNull() 1294 private JSONBuffer startLogMessage(@NotNull final String messageType, 1295 @Nullable final OperationType operationType, 1296 @NotNull final LDAPConnectionInfo connectionInfo, 1297 final int messageID) 1298 { 1299 JSONBuffer buffer = jsonBuffers.get(); 1300 if (buffer == null) 1301 { 1302 buffer = new JSONBuffer(); 1303 jsonBuffers.set(buffer); 1304 } 1305 else 1306 { 1307 buffer.clear(); 1308 } 1309 1310 buffer.beginObject(); 1311 1312 SimpleDateFormat timestampFormatter = timestampFormatters.get(); 1313 if (timestampFormatter == null) 1314 { 1315 timestampFormatter = 1316 new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSS'Z'"); 1317 timestampFormatter.setTimeZone(StaticUtils.getUTCTimeZone()); 1318 timestampFormatters.set(timestampFormatter); 1319 } 1320 1321 buffer.appendString("timestamp", timestampFormatter.format(new Date())); 1322 buffer.appendString("message-type", messageType); 1323 1324 if (operationType != null) 1325 { 1326 switch (operationType) 1327 { 1328 case ABANDON: 1329 buffer.appendString("operation-type", "abandon"); 1330 break; 1331 case ADD: 1332 buffer.appendString("operation-type", "add"); 1333 break; 1334 case BIND: 1335 buffer.appendString("operation-type", "bind"); 1336 break; 1337 case COMPARE: 1338 buffer.appendString("operation-type", "compare"); 1339 break; 1340 case DELETE: 1341 buffer.appendString("operation-type", "delete"); 1342 break; 1343 case EXTENDED: 1344 buffer.appendString("operation-type", "extended"); 1345 break; 1346 case MODIFY: 1347 buffer.appendString("operation-type", "modify"); 1348 break; 1349 case MODIFY_DN: 1350 buffer.appendString("operation-type", "modify-dn"); 1351 break; 1352 case SEARCH: 1353 buffer.appendString("operation-type", "search"); 1354 break; 1355 case UNBIND: 1356 buffer.appendString("operation-type", "unbind"); 1357 break; 1358 } 1359 } 1360 1361 buffer.appendNumber("connection-id", connectionInfo.getConnectionID()); 1362 1363 final String connectionName = connectionInfo.getConnectionName(); 1364 if (connectionName != null) 1365 { 1366 buffer.appendString("connection-name", connectionName); 1367 } 1368 1369 final String connectionPoolName = connectionInfo.getConnectionPoolName(); 1370 if (connectionPoolName != null) 1371 { 1372 buffer.appendString("connection-pool-name", connectionPoolName); 1373 } 1374 1375 if (messageID >= 0) 1376 { 1377 buffer.appendNumber("ldap-message-id", messageID); 1378 } 1379 1380 return buffer; 1381 } 1382 1383 1384 1385 /** 1386 * Appends information about an exception to the provided buffer. 1387 * 1388 * @param buffer The buffer to which the exception should be appended. 1389 * It must not be {@code null}. 1390 * @param fieldName The name of the field to use for the exception 1391 * object that is appended to the buffer. It must not be 1392 * {@code null}. 1393 * @param exception The exception to be appended. It must not be 1394 * {@code null}. 1395 */ 1396 private void appendException(@NotNull final JSONBuffer buffer, 1397 @NotNull final String fieldName, 1398 @NotNull final Throwable exception) 1399 { 1400 buffer.beginObject(fieldName); 1401 1402 buffer.appendString("exception-class", exception.getClass().getName()); 1403 1404 final String message = exception.getMessage(); 1405 if (message != null) 1406 { 1407 buffer.appendString("message", message); 1408 } 1409 1410 buffer.beginArray("stack-trace-frames"); 1411 for (final StackTraceElement frame : exception.getStackTrace()) 1412 { 1413 buffer.beginObject(); 1414 1415 buffer.appendString("class", frame.getClassName()); 1416 buffer.appendString("method", frame.getMethodName()); 1417 1418 final String fileName = frame.getFileName(); 1419 if (fileName != null) 1420 { 1421 buffer.appendString("file", fileName); 1422 } 1423 1424 if (frame.isNativeMethod()) 1425 { 1426 buffer.appendBoolean("is-native-method", true); 1427 } 1428 else 1429 { 1430 final int lineNumber = frame.getLineNumber(); 1431 if (lineNumber > 0) 1432 { 1433 buffer.appendNumber("line-number", lineNumber); 1434 } 1435 } 1436 1437 buffer.endObject(); 1438 } 1439 buffer.endArray(); 1440 1441 final Throwable cause = exception.getCause(); 1442 if (cause != null) 1443 { 1444 appendException(buffer, "caused-by", cause); 1445 } 1446 1447 buffer.endObject(); 1448 } 1449 1450 1451 1452 /** 1453 * Appends information about the given set of controls to the provided buffer, 1454 * if control OIDs should be included in log messages. 1455 * 1456 * @param buffer The buffer to which the information should be appended. 1457 * It must not be {@code null}. 1458 * @param fieldName The name to use for the JSON field. It must not be 1459 * {@code null}. 1460 * @param controls The controls to be appended. It must not be 1461 * {@code null} but may be empty. 1462 */ 1463 private void appendControls(@NotNull final JSONBuffer buffer, 1464 @NotNull final String fieldName, 1465 @NotNull final Control... controls) 1466 { 1467 if (includeControlOIDs && (controls.length > 0)) 1468 { 1469 buffer.beginArray(fieldName); 1470 for (final Control c : controls) 1471 { 1472 buffer.appendString(c.getOID()); 1473 } 1474 buffer.endArray(); 1475 } 1476 } 1477 1478 1479 1480 /** 1481 * Appends information about the given set of controls to the provided buffer, 1482 * if control OIDs should be included in log messages. 1483 * 1484 * @param buffer The buffer to which the information should be appended. 1485 * It must not be {@code null}. 1486 * @param fieldName The name to use for the JSON field. It must not be 1487 * {@code null}. 1488 * @param controls The controls to be appended. It must not be 1489 * {@code null} but may be empty. 1490 */ 1491 private void appendControls(@NotNull final JSONBuffer buffer, 1492 @NotNull final String fieldName, 1493 @NotNull final List<Control> controls) 1494 { 1495 if (includeControlOIDs && (! controls.isEmpty())) 1496 { 1497 buffer.beginArray(fieldName); 1498 for (final Control c : controls) 1499 { 1500 buffer.appendString(c.getOID()); 1501 } 1502 buffer.endArray(); 1503 } 1504 } 1505 1506 1507 1508 /** 1509 * Appends a DN to the provided buffer, redacting any attribute values as 1510 * appropriate. 1511 * 1512 * @param buffer The buffer to which the information should be appended. 1513 * It must not be {@code null}. 1514 * @param fieldName The name to use for the JSON field. It must not be 1515 * {@code null}. 1516 * @param dn The DN to be appended. It must not be {@code null} but 1517 * may be empty. 1518 */ 1519 private void appendDN(@NotNull final JSONBuffer buffer, 1520 @NotNull final String fieldName, 1521 @NotNull final String dn) 1522 { 1523 if (fullAttributesToRedact.isEmpty()) 1524 { 1525 buffer.appendString(fieldName, dn); 1526 return; 1527 } 1528 1529 final DN parsedDN; 1530 try 1531 { 1532 parsedDN = new DN(dn); 1533 } 1534 catch (final Exception e) 1535 { 1536 Debug.debugException(e); 1537 buffer.appendString(fieldName, dn); 1538 return; 1539 } 1540 1541 boolean redactionNeeded = false; 1542 final RDN[] originalRDNs = parsedDN.getRDNs(); 1543 for (final RDN rdn : originalRDNs) 1544 { 1545 for (final String attributeName : rdn.getAttributeNames()) 1546 { 1547 if (fullAttributesToRedact.contains( 1548 StaticUtils.toLowerCase(attributeName))) 1549 { 1550 redactionNeeded = true; 1551 break; 1552 } 1553 } 1554 } 1555 1556 if (redactionNeeded) 1557 { 1558 final RDN[] newRDNs = new RDN[originalRDNs.length]; 1559 for (int i=0; i < originalRDNs.length; i++) 1560 { 1561 final RDN rdn = originalRDNs[i]; 1562 final String[] names = rdn.getAttributeNames(); 1563 final byte[][] values = new byte[names.length][]; 1564 for (int j=0; j < names.length; j++) 1565 { 1566 final String lowerName = StaticUtils.toLowerCase(names[j]); 1567 if (fullAttributesToRedact.contains(lowerName)) 1568 { 1569 values[j] = REDACTED_VALUE_BYTES; 1570 } 1571 else 1572 { 1573 values[j] = rdn.getByteArrayAttributeValues()[j]; 1574 } 1575 } 1576 1577 newRDNs[i] = new RDN(names, values, rdn.getSchema()); 1578 } 1579 1580 buffer.appendString(fieldName, new DN(newRDNs).toString()); 1581 } 1582 else 1583 { 1584 buffer.appendString(fieldName, dn); 1585 } 1586 } 1587 1588 1589 1590 /** 1591 * Appends the given list of attributes to the provided buffer, redacting any 1592 * values as appropriate. 1593 * 1594 * @param buffer The buffer to which the information should be 1595 * appended. It must not be {@code null}. 1596 * @param fieldName The name of the field to use for the attribute 1597 * array. It must not be {@code null}. 1598 * @param attributes The attributes to be appended. It must not be 1599 * {@code null}, but may be empty. 1600 * @param includeValues Indicates whether to include the values of the 1601 * attributes. 1602 */ 1603 private void appendAttributes(@NotNull final JSONBuffer buffer, 1604 @NotNull final String fieldName, 1605 @NotNull final List<Attribute> attributes, 1606 final boolean includeValues) 1607 { 1608 buffer.beginArray(fieldName); 1609 1610 for (final Attribute attribute : attributes) 1611 { 1612 if (includeValues) 1613 { 1614 buffer.beginObject(); 1615 buffer.appendString("name", attribute.getName()); 1616 buffer.beginArray("values"); 1617 1618 final String baseName = 1619 StaticUtils.toLowerCase(attribute.getBaseName()); 1620 if (fullAttributesToRedact.contains(baseName)) 1621 { 1622 for (final String value : attribute.getValues()) 1623 { 1624 buffer.appendString(REDACTED_VALUE_STRING); 1625 } 1626 } 1627 else 1628 { 1629 for (final String value : attribute.getValues()) 1630 { 1631 buffer.appendString(value); 1632 } 1633 } 1634 1635 buffer.endArray(); 1636 buffer.endObject(); 1637 } 1638 else 1639 { 1640 buffer.appendString(attribute.getName()); 1641 } 1642 } 1643 1644 buffer.endArray(); 1645 } 1646 1647 1648 1649 /** 1650 * Redacts the provided filter, if necessary. 1651 * 1652 * @param filter The filter to be redacted. It must not be {@code null}. 1653 * 1654 * @return The redacted filter. 1655 */ 1656 @NotNull() 1657 private Filter redactFilter(@NotNull final Filter filter) 1658 { 1659 switch (filter.getFilterType()) 1660 { 1661 case Filter.FILTER_TYPE_AND: 1662 final Filter[] currentANDComps = filter.getComponents(); 1663 final Filter[] newANDComps = new Filter[currentANDComps.length]; 1664 for (int i=0; i < currentANDComps.length; i++) 1665 { 1666 newANDComps[i] = redactFilter(currentANDComps[i]); 1667 } 1668 return Filter.createANDFilter(newANDComps); 1669 1670 case Filter.FILTER_TYPE_OR: 1671 final Filter[] currentORComps = filter.getComponents(); 1672 final Filter[] newORComps = new Filter[currentORComps.length]; 1673 for (int i=0; i < currentORComps.length; i++) 1674 { 1675 newORComps[i] = redactFilter(currentORComps[i]); 1676 } 1677 return Filter.createORFilter(newORComps); 1678 1679 case Filter.FILTER_TYPE_NOT: 1680 return Filter.createNOTFilter(redactFilter(filter.getNOTComponent())); 1681 1682 case Filter.FILTER_TYPE_EQUALITY: 1683 return Filter.createEqualityFilter(filter.getAttributeName(), 1684 redactAssertionValue(filter)); 1685 1686 case Filter.FILTER_TYPE_GREATER_OR_EQUAL: 1687 return Filter.createGreaterOrEqualFilter(filter.getAttributeName(), 1688 redactAssertionValue(filter)); 1689 1690 case Filter.FILTER_TYPE_LESS_OR_EQUAL: 1691 return Filter.createLessOrEqualFilter(filter.getAttributeName(), 1692 redactAssertionValue(filter)); 1693 1694 case Filter.FILTER_TYPE_APPROXIMATE_MATCH: 1695 return Filter.createApproximateMatchFilter(filter.getAttributeName(), 1696 redactAssertionValue(filter)); 1697 1698 case Filter.FILTER_TYPE_EXTENSIBLE_MATCH: 1699 return Filter.createExtensibleMatchFilter(filter.getAttributeName(), 1700 filter.getMatchingRuleID(), filter.getDNAttributes(), 1701 redactAssertionValue(filter)); 1702 1703 case Filter.FILTER_TYPE_SUBSTRING: 1704 final String baseName = StaticUtils.toLowerCase(Attribute.getBaseName( 1705 filter.getAttributeName())); 1706 if (fullAttributesToRedact.contains(baseName)) 1707 { 1708 final String[] redactedSubAnyStrings = 1709 new String[filter.getSubAnyStrings().length]; 1710 Arrays.fill(redactedSubAnyStrings, REDACTED_VALUE_STRING); 1711 1712 return Filter.createSubstringFilter(filter.getAttributeName(), 1713 filter.getSubInitialString() == null 1714 ? null 1715 : REDACTED_VALUE_STRING, 1716 redactedSubAnyStrings, 1717 filter.getSubFinalString() == null 1718 ? null 1719 : REDACTED_VALUE_STRING); 1720 } 1721 else 1722 { 1723 return Filter.createSubstringFilter(filter.getAttributeName(), 1724 filter.getSubInitialString(), filter.getSubAnyStrings(), 1725 filter.getSubFinalString()); 1726 } 1727 1728 case Filter.FILTER_TYPE_PRESENCE: 1729 default: 1730 return filter; 1731 } 1732 } 1733 1734 1735 1736 /** 1737 * Retrieves an assertion value to use for a redacted filter. 1738 * 1739 * @param filter The filter for which to obtain the assertion value. 1740 * 1741 * @return The assertion value to use for a redacted filter. 1742 */ 1743 @NotNull() 1744 private String redactAssertionValue(@NotNull final Filter filter) 1745 { 1746 final String attributeName = filter.getAttributeName(); 1747 if (attributeName == null) 1748 { 1749 return filter.getAssertionValue(); 1750 } 1751 1752 final String baseName = 1753 StaticUtils.toLowerCase(Attribute.getBaseName(attributeName)); 1754 if (fullAttributesToRedact.contains(baseName)) 1755 { 1756 return REDACTED_VALUE_STRING; 1757 } 1758 else 1759 { 1760 return filter.getAssertionValue(); 1761 } 1762 } 1763 1764 1765 1766 /** 1767 * Logs a final result message for the provided result. If the result is a 1768 * {@code BindResult}, an {@code ExtendedResult}, or a {@code SearchResult}, 1769 * then additional information about that type of result may also be included. 1770 * 1771 * @param connectionInfo Information about the connection with which the 1772 * result is associated. It must not be 1773 * {@code null}. 1774 * @param operationType The operation type for the log message. It must 1775 * not be {@code null}. 1776 * @param messageID The LDAP message ID for the associated operation. 1777 * @param result The result to be logged. 1778 */ 1779 private void logLDAPResult(@NotNull final LDAPConnectionInfo connectionInfo, 1780 @NotNull final OperationType operationType, 1781 final int messageID, 1782 @NotNull final LDAPResult result) 1783 { 1784 if (logFinalResults && operationTypes.contains(operationType)) 1785 { 1786 final JSONBuffer buffer = startLogMessage("result", operationType, 1787 connectionInfo, messageID); 1788 1789 buffer.appendNumber("result-code-value", 1790 result.getResultCode().intValue()); 1791 buffer.appendString("result-code-name", result.getResultCode().getName()); 1792 1793 final String diagnosticMessage = result.getDiagnosticMessage(); 1794 if (diagnosticMessage != null) 1795 { 1796 buffer.appendString("diagnostic-message", diagnosticMessage); 1797 } 1798 1799 final String matchedDN = result.getMatchedDN(); 1800 if (matchedDN != null) 1801 { 1802 buffer.appendString("matched-dn", matchedDN); 1803 } 1804 1805 final String[] referralURLs = result.getReferralURLs(); 1806 if ((referralURLs != null) && (referralURLs.length > 0)) 1807 { 1808 buffer.beginArray("referral-urls"); 1809 for (final String url : referralURLs) 1810 { 1811 buffer.appendString(url); 1812 } 1813 buffer.endArray(); 1814 } 1815 1816 if (result instanceof BindResult) 1817 { 1818 final BindResult bindResult = (BindResult) result; 1819 if (bindResult.getServerSASLCredentials() != null) 1820 { 1821 buffer.appendBoolean("has-server-sasl-credentials", true); 1822 } 1823 } 1824 else if (result instanceof ExtendedResult) 1825 { 1826 final ExtendedResult extendedResult = (ExtendedResult) result; 1827 final String oid = extendedResult.getOID(); 1828 if (oid != null) 1829 { 1830 buffer.appendString("oid", oid); 1831 } 1832 1833 buffer.appendBoolean("has-value", (extendedResult.getValue() != null)); 1834 } 1835 1836 appendControls(buffer, "control-oids", result.getResponseControls()); 1837 1838 logMessage(buffer, flushAfterFinalResultMessages); 1839 } 1840 } 1841 1842 1843 1844 /** 1845 * Finalizes the message and writes it to the log handler, optionally flushing 1846 * the handler after the message has been written. 1847 * 1848 * @param buffer The buffer containing the message to be written. 1849 * @param flushHandler Indicates whether to flush the handler after the 1850 * message has been written. 1851 */ 1852 private void logMessage(@NotNull final JSONBuffer buffer, 1853 final boolean flushHandler) 1854 { 1855 buffer.endObject(); 1856 1857 logHandler.publish(new LogRecord(Level.INFO, buffer.toString())); 1858 1859 if (flushHandler) 1860 { 1861 logHandler.flush(); 1862 } 1863 } 1864}