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