Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2020-2024 Ping Identity Corporation
* All Rights Reserved.
*/
/*
* Copyright 2020-2024 Ping Identity Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright (C) 2020-2024 Ping Identity Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPLv2 only)
* or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
package com.unboundid.ldap.sdk;
import java.net.InetAddress;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
import com.unboundid.ldap.sdk.schema.Schema;
import com.unboundid.util.Debug;
import com.unboundid.util.NotMutable;
import com.unboundid.util.NotNull;
import com.unboundid.util.Nullable;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.json.JSONBuffer;
/**
* This class provides an implementation of an LDAP connection access logger
* that records messages as JSON objects.
*/
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class JSONLDAPConnectionLogger
extends LDAPConnectionLogger
{
/**
* The bytes that comprise the value that will be used in place of redacted
* attribute values.
*/
@NotNull private static final String REDACTED_VALUE_STRING = "[REDACTED]";
/**
* The bytes that comprise the value that will be used in place of redacted
* attribute values.
*/
@NotNull private static final byte[] REDACTED_VALUE_BYTES =
StaticUtils.getBytes(REDACTED_VALUE_STRING);
// Indicates whether to flush the handler after logging information about each
// successful for failed connection attempt.
private final boolean flushAfterConnectMessages;
// Indicates whether to flush the handler after logging information about each
// disconnect.
private final boolean flushAfterDisconnectMessages;
// Indicates whether to flush the handler after logging information about each
// request.
private final boolean flushAfterRequestMessages;
// Indicates whether to flush the handler after logging information about the
// final result for each operation.
private final boolean flushAfterFinalResultMessages;
// Indicates whether to flush the handler after logging information about each
// non-final result (including search result entries, search result
// references, and intermediate response messages) for each operation.
private final boolean flushAfterNonFinalResultMessages;
// Indicates whether to include the names of attributes provided in add
// requests.
private final boolean includeAddAttributeNames;
// Indicates whether to include the values of attributes provided in add
// requests.
private final boolean includeAddAttributeValues;
// Indicates whether to include the names of attributes targeted by modify
// requests.
private final boolean includeModifyAttributeNames;
// Indicates whether to include the values of attributes targeted by modify
// requests.
private final boolean includeModifyAttributeValues;
// Indicates whether to include the OIDs of controls included in requests and
// results.
private final boolean includeControlOIDs;
// Indicates whether to include the names of attributes provided in search
// result entries.
private final boolean includeSearchEntryAttributeNames;
// Indicates whether to include the values of attributes provided in search
// result entries.
private final boolean includeSearchEntryAttributeValues;
// Indicates whether to log successful and failed connection attempts.
private final boolean logConnects;
// Indicates whether to log disconnects.
private final boolean logDisconnects;
// Indicates whether to log intermediate response messages.
private final boolean logIntermediateResponses;
// Indicates whether to log operation requests for enabled operation types.
private final boolean logRequests;
// Indicates whether to log final operation results for enabled operation
// types.
private final boolean logFinalResults;
// Indicates whether to log search result entries.
private final boolean logSearchEntries;
// Indicates whether to log search result references.
private final boolean logSearchReferences;
// The log handler that will be used to actually log the messages.
@NotNull private final Handler logHandler;
// The schema to use for identifying alternate attribute type names.
@Nullable private final Schema schema;
// The types of operations for which requests should be logged.
@NotNull private final Set operationTypes;
// The names or OIDs of the attributes whose values should be redacted.
@NotNull private final Set attributesToRedact;
// The full set of the names and OIDs for attributes whose values should be
// redacted.
@NotNull private final Set fullAttributesToRedact;
// The set of thread-local JSON buffers that will be used for formatting log
// messages.
@NotNull private final ThreadLocal jsonBuffers;
// The set of thread-local date formatters that will be used for formatting
// timestamps.
@NotNull private final ThreadLocal timestampFormatters;
/**
* Creates a new instance of this LDAP connection logger that will write
* messages to the provided log handler using the given set of properties.
*
* @param logHandler The log handler that will be used to actually log the
* messages. All messages will be logged with a level of
* {@code INFO}.
* @param properties The properties to use for this logger.
*/
public JSONLDAPConnectionLogger(@NotNull final Handler logHandler,
@NotNull final JSONLDAPConnectionLoggerProperties properties)
{
this.logHandler = logHandler;
flushAfterConnectMessages = properties.flushAfterConnectMessages();
flushAfterDisconnectMessages = properties.flushAfterDisconnectMessages();
flushAfterRequestMessages = properties.flushAfterRequestMessages();
flushAfterFinalResultMessages =
properties.flushAfterFinalResultMessages();
flushAfterNonFinalResultMessages =
properties.flushAfterNonFinalResultMessages();
includeAddAttributeNames = properties.includeAddAttributeNames();
includeAddAttributeValues = properties.includeAddAttributeValues();
includeModifyAttributeNames = properties.includeModifyAttributeNames();
includeModifyAttributeValues = properties.includeModifyAttributeValues();
includeControlOIDs = properties.includeControlOIDs();
includeSearchEntryAttributeNames =
properties.includeSearchEntryAttributeNames();
includeSearchEntryAttributeValues =
properties.includeSearchEntryAttributeValues();
logConnects = properties.logConnects();
logDisconnects = properties.logDisconnects();
logIntermediateResponses = properties.logIntermediateResponses();
logRequests = properties.logRequests();
logFinalResults = properties.logFinalResults();
logSearchEntries = properties.logSearchEntries();
logSearchReferences = properties.logSearchReferences();
schema = properties.getSchema();
attributesToRedact = Collections.unmodifiableSet(new LinkedHashSet<>(
properties.getAttributesToRedact()));
final EnumSet opTypes = EnumSet.noneOf(OperationType.class);
opTypes.addAll(properties.getOperationTypes());
operationTypes = Collections.unmodifiableSet(opTypes);
jsonBuffers = new ThreadLocal<>();
timestampFormatters = new ThreadLocal<>();
final Set fullAttrsToRedact = new HashSet<>();
for (final String attr : attributesToRedact)
{
fullAttrsToRedact.add(StaticUtils.toLowerCase(attr));
if (schema != null)
{
final AttributeTypeDefinition d = schema.getAttributeType(attr);
if (d != null)
{
fullAttrsToRedact.add(StaticUtils.toLowerCase(d.getOID()));
for (final String name : d.getNames())
{
fullAttrsToRedact.add(StaticUtils.toLowerCase(name));
}
}
}
}
fullAttributesToRedact = Collections.unmodifiableSet(fullAttrsToRedact);
}
/**
* Indicates whether to log successful and failed connection attempts.
* Connection attempts will be logged by default.
*
* @return {@code true} if connection attempts should be logged, or
* {@code false} if not.
*/
public boolean logConnects()
{
return logConnects;
}
/**
* Indicates whether to log disconnects. Disconnects will be logged by
* default.
*
* @return {@code true} if disconnects should be logged, or {@code false} if
* not.
*/
public boolean logDisconnects()
{
return logDisconnects;
}
/**
* Indicates whether to log messages about requests for operations included
* in the set of operation types returned by the {@link #getOperationTypes}
* method. Operation requests will be logged by default.
*
* @return {@code true} if operation requests should be logged for
* appropriate operation types, or {@code false} if not.
*/
public boolean logRequests()
{
return logRequests;
}
/**
* Indicates whether to log messages about the final reults for operations
* included in the set of operation types returned by the
* {@link #getOperationTypes} method. Final operation results will be
* logged by default.
*
* @return {@code true} if operation requests should be logged for
* appropriate operation types, or {@code false} if not.
*/
public boolean logFinalResults()
{
return logFinalResults;
}
/**
* Indicates whether to log messages about each search result entry returned
* for search operations. This property will only be used if the set returned
* by the {@link #getOperationTypes} method includes
* {@link OperationType#SEARCH}. Search result entries will not be logged by
* default.
*
* @return {@code true} if search result entries should be logged, or
* {@code false} if not.
*/
public boolean logSearchEntries()
{
return logSearchEntries;
}
/**
* Indicates whether to log messages about each search result reference
* returned for search operations. This property will only be used if the set
* returned by the {@link #getOperationTypes} method includes
* {@link OperationType#SEARCH}. Search result references will not be logged
* by default.
*
* @return {@code true} if search result references should be logged, or
* {@code false} if not.
*/
public boolean logSearchReferences()
{
return logSearchReferences;
}
/**
* Indicates whether to log messages about each intermediate response returned
* in the course of processing an operation. Intermediate response messages
* will be logged by default.
*
* @return {@code true} if intermediate response messages should be logged,
* or {@code false} if not.
*/
public boolean logIntermediateResponses()
{
return logIntermediateResponses;
}
/**
* Retrieves the set of operation types for which to log requests and
* results. All operation types will be logged by default.
*
* @return The set of operation types for which to log requests and results.
*/
@NotNull()
public Set getOperationTypes()
{
return operationTypes;
}
/**
* Indicates whether log messages about add requests should include the names
* of the attributes provided in the request. Add attribute names (but not
* values) will be logged by default.
*
* @return {@code true} if add attribute names should be logged, or
* {@code false} if not.
*/
public boolean includeAddAttributeNames()
{
return includeAddAttributeNames;
}
/**
* Indicates whether log messages about add requests should include the values
* of the attributes provided in the request. This property will only be used
* if {@link #includeAddAttributeNames} returns {@code true}. Values for
* attributes named in the set returned by the
* {@link #getAttributesToRedact} method will be replaced with a value of
* "[REDACTED]". Add attribute names (but not values) will be
* logged by default.
*
* @return {@code true} if add attribute values should be logged, or
* {@code false} if not.
*/
public boolean includeAddAttributeValues()
{
return includeAddAttributeValues;
}
/**
* Indicates whether log messages about modify requests should include the
* names of the attributes modified in the request. Modified attribute names
* (but not values) will be logged by default.
*
* @return {@code true} if modify attribute names should be logged, or
* {@code false} if not.
*/
public boolean includeModifyAttributeNames()
{
return includeModifyAttributeNames;
}
/**
* Indicates whether log messages about modify requests should include the
* values of the attributes modified in the request. This property will only
* be used if {@link #includeModifyAttributeNames} returns {@code true}.
* Values for attributes named in the set returned by the
* {@link #getAttributesToRedact} method will be replaced with a value of
* "[REDACTED]". Modify attribute names (but not values) will be
* logged by default.
*
* @return {@code true} if modify attribute values should be logged, or
* {@code false} if not.
*/
public boolean includeModifyAttributeValues()
{
return includeModifyAttributeValues;
}
/**
* Indicates whether log messages about search result entries should include
* the names of the attributes in the returned entry. Entry attribute names
* (but not values) will be logged by default.
*
* @return {@code true} if search result entry attribute names should be
* logged, or {@code false} if not.
*/
public boolean includeSearchEntryAttributeNames()
{
return includeSearchEntryAttributeNames;
}
/**
* Indicates whether log messages about search result entries should include
* the values of the attributes in the returned entry. This property will
* only be used if {@link #includeSearchEntryAttributeNames} returns
* {@code true}. Values for attributes named in the set returned by the
* {@link #getAttributesToRedact} method will be replaced with a value of
* "[REDACTED]". Entry attribute names (but not values) will be
* logged by default.
*
* @return {@code true} if search result entry attribute values should be
* logged, or {@code false} if not.
*/
public boolean includeSearchEntryAttributeValues()
{
return includeSearchEntryAttributeValues;
}
/**
* Retrieves a set containing the names or OIDs of the attributes whose values
* should be redacted from log messages. Values of the userPassword,
* authPassword, and unicodePWD attributes will be redacted by default.
*
* @return A set containing the names or OIDs of the attributes whose values
* should be redacted from log messages, or an empty set if no
* attribute values should be redacted.
*/
@NotNull()
public Set getAttributesToRedact()
{
return attributesToRedact;
}
/**
* Indicates whether request and result log messages should include the OIDs
* of any controls included in that request or result. Control OIDs will
* be logged by default.
*
* @return {@code true} if request control OIDs should be logged, or
* {@code false} if not.
*/
public boolean includeControlOIDs()
{
return includeControlOIDs;
}
/**
* Indicates whether the log handler should be flushed after logging each
* successful or failed connection attempt. By default, the handler will be
* flushed after logging each connection attempt.
*
* @return {@code true} if the log handler should be flushed after logging
* each connection attempt, or {@code false} if not.
*/
public boolean flushAfterConnectMessages()
{
return flushAfterConnectMessages;
}
/**
* Indicates whether the log handler should be flushed after logging each
* disconnect. By default, the handler will be flushed after logging each
* disconnect.
*
* @return {@code true} if the log handler should be flushed after logging
* each disconnect, or {@code false} if not.
*/
public boolean flushAfterDisconnectMessages()
{
return flushAfterDisconnectMessages;
}
/**
* Indicates whether the log handler should be flushed after logging each
* request. By default, the handler will be flushed after logging each final
* result, but not after logging requests or non-final results.
*
* @return {@code true} if the log handler should be flushed after logging
* each request, or {@code false} if not.
*/
public boolean flushAfterRequestMessages()
{
return flushAfterRequestMessages;
}
/**
* Indicates whether the log handler should be flushed after logging each
* non-final result (including search result entries, search result
* references, and intermediate response messages). By default, the handler
* will be flushed after logging each final result, but not after logging
* requests or non-final results.
*
* @return {@code true} if the log handler should be flushed after logging
* each non-final result, or {@code false} if not.
*/
public boolean flushAfterNonFinalResultMessages()
{
return flushAfterNonFinalResultMessages;
}
/**
* Indicates whether the log handler should be flushed after logging the final
* result for each operation. By default, the handler will be flushed after
* logging each final result, but not after logging requests or non-final
* results.
*
* @return {@code true} if the log handler should be flushed after logging
* each final result, or {@code false} if not.
*/
public boolean flushAfterFinalResultMessages()
{
return flushAfterFinalResultMessages;
}
/**
* Retrieves the schema that will be used to identify alternate names and OIDs
* for attributes whose values should be redacted. The LDAP SDK's default
* standard schema will be used by default.
*
* @return The schema that will be used to identify alternate names and OIDs
* for attributes whose values should be redacted, or {@code null}
* if no schema should be used.
*/
@Nullable()
public Schema getSchema()
{
return schema;
}
/**
* {@inheritDoc}
*/
@Override()
public void logConnect(@NotNull final LDAPConnectionInfo connectionInfo,
@NotNull final String host,
@NotNull final InetAddress inetAddress,
final int port)
{
if (logConnects)
{
final JSONBuffer buffer = startLogMessage("connect", null,
connectionInfo, -1);
buffer.appendString("hostname", host);
buffer.appendString("ip-address", inetAddress.getHostAddress());
buffer.appendNumber("port", port);
logMessage(buffer, flushAfterConnectMessages);
}
}
/**
* {@inheritDoc}
*/
@Override()
public void logConnectFailure(
@NotNull final LDAPConnectionInfo connectionInfo,
@NotNull final String host, final int port,
@NotNull final LDAPException connectException)
{
if (logConnects)
{
final JSONBuffer buffer = startLogMessage("connect-failure", null,
connectionInfo, -1);
buffer.appendString("hostname", host);
buffer.appendNumber("port", port);
if (connectException != null)
{
appendException(buffer, "connect-exception", connectException);
}
logMessage(buffer, flushAfterConnectMessages);
}
}
/**
* {@inheritDoc}
*/
@Override()
public void logDisconnect(
@NotNull final LDAPConnectionInfo connectionInfo,
@NotNull final String host, final int port,
@NotNull final DisconnectType disconnectType,
@Nullable final String disconnectMessage,
@Nullable final Throwable disconnectCause)
{
if (logDisconnects)
{
final JSONBuffer buffer = startLogMessage("disconnect", null,
connectionInfo, -1);
buffer.appendString("hostname", host);
buffer.appendNumber("port", port);
buffer.appendString("disconnect-type", disconnectType.name());
if (disconnectMessage != null)
{
buffer.appendString("disconnect-message", disconnectMessage);
}
if (disconnectCause != null)
{
appendException(buffer, "disconnect-cause", disconnectCause);
}
logMessage(buffer, flushAfterDisconnectMessages);
}
}
/**
* {@inheritDoc}
*/
@Override()
public void logAbandonRequest(
@NotNull final LDAPConnectionInfo connectionInfo,
final int messageID,
final int messageIDToAbandon,
@NotNull final List requestControls)
{
if (logRequests && operationTypes.contains(OperationType.ABANDON))
{
final JSONBuffer buffer = startLogMessage("request",
OperationType.ABANDON, connectionInfo, messageID);
buffer.appendNumber("message-id-to-abandon", messageIDToAbandon);
appendControls(buffer, "control-oids", requestControls);
logMessage(buffer, flushAfterRequestMessages);
}
}
/**
* {@inheritDoc}
*/
@Override()
public void logAddRequest(@NotNull final LDAPConnectionInfo connectionInfo,
final int messageID,
@NotNull final ReadOnlyAddRequest addRequest)
{
if (logRequests && operationTypes.contains(OperationType.ADD))
{
final JSONBuffer buffer = startLogMessage("request",
OperationType.ADD, connectionInfo, messageID);
appendDN(buffer, "dn", addRequest.getDN());
if (includeAddAttributeNames)
{
appendAttributes(buffer, "attributes", addRequest.getAttributes(),
includeAddAttributeValues);
}
appendControls(buffer, "control-oids", addRequest.getControls());
logMessage(buffer, flushAfterRequestMessages);
}
}
/**
* {@inheritDoc}
*/
@Override()
public void logAddResult(@NotNull final LDAPConnectionInfo connectionInfo,
final int requestMessageID,
@NotNull final LDAPResult addResult)
{
logLDAPResult(connectionInfo, OperationType.ADD, requestMessageID,
addResult);
}
/**
* {@inheritDoc}
*/
@Override()
public void logBindRequest(@NotNull final LDAPConnectionInfo connectionInfo,
final int messageID,
@NotNull final SimpleBindRequest bindRequest)
{
if (logRequests && operationTypes.contains(OperationType.BIND))
{
final JSONBuffer buffer = startLogMessage("request",
OperationType.BIND, connectionInfo, messageID);
buffer.appendString("authentication-type", "simple");
appendDN(buffer, "dn", bindRequest.getBindDN());
appendControls(buffer, "control-oids", bindRequest.getControls());
logMessage(buffer, flushAfterRequestMessages);
}
}
/**
* {@inheritDoc}
*/
@Override()
public void logBindRequest(@NotNull final LDAPConnectionInfo connectionInfo,
final int messageID,
@NotNull final SASLBindRequest bindRequest)
{
if (logRequests && operationTypes.contains(OperationType.BIND))
{
final JSONBuffer buffer = startLogMessage("request",
OperationType.BIND, connectionInfo, messageID);
buffer.appendString("authentication-type", "SASL");
buffer.appendString("sasl-mechanism", bindRequest.getSASLMechanismName());
appendControls(buffer, "control-oids", bindRequest.getControls());
logMessage(buffer, flushAfterRequestMessages);
}
}
/**
* {@inheritDoc}
*/
@Override()
public void logBindResult(@NotNull final LDAPConnectionInfo connectionInfo,
final int requestMessageID,
@NotNull final BindResult bindResult)
{
logLDAPResult(connectionInfo, OperationType.BIND, requestMessageID,
bindResult);
}
/**
* {@inheritDoc}
*/
@Override()
public void logCompareRequest(
@NotNull final LDAPConnectionInfo connectionInfo,
final int messageID,
@NotNull final ReadOnlyCompareRequest compareRequest)
{
if (logRequests && operationTypes.contains(OperationType.COMPARE))
{
final JSONBuffer buffer = startLogMessage("request",
OperationType.COMPARE, connectionInfo, messageID);
appendDN(buffer, "dn", compareRequest.getDN());
appendDN(buffer, "attribute-type", compareRequest.getAttributeName());
final String baseName = StaticUtils.toLowerCase(
Attribute.getBaseName(compareRequest.getAttributeName()));
if (fullAttributesToRedact.contains(baseName))
{
buffer.appendString("assertion-value", REDACTED_VALUE_STRING);
}
else
{
buffer.appendString("assertion-value",
compareRequest.getAssertionValue());
}
appendControls(buffer, "control-oids", compareRequest.getControls());
logMessage(buffer, flushAfterRequestMessages);
}
}
/**
* {@inheritDoc}
*/
@Override()
public void logCompareResult(@NotNull final LDAPConnectionInfo connectionInfo,
final int requestMessageID,
@NotNull final LDAPResult compareResult)
{
logLDAPResult(connectionInfo, OperationType.COMPARE, requestMessageID,
compareResult);
}
/**
* {@inheritDoc}
*/
@Override()
public void logDeleteRequest(@NotNull final LDAPConnectionInfo connectionInfo,
final int messageID,
@NotNull final ReadOnlyDeleteRequest deleteRequest)
{
if (logRequests && operationTypes.contains(OperationType.DELETE))
{
final JSONBuffer buffer = startLogMessage("request",
OperationType.DELETE, connectionInfo, messageID);
appendDN(buffer, "dn", deleteRequest.getDN());
appendControls(buffer, "control-oids", deleteRequest.getControls());
logMessage(buffer, flushAfterRequestMessages);
}
}
/**
* {@inheritDoc}
*/
@Override()
public void logDeleteResult(@NotNull final LDAPConnectionInfo connectionInfo,
final int requestMessageID,
@NotNull final LDAPResult deleteResult)
{
logLDAPResult(connectionInfo, OperationType.DELETE, requestMessageID,
deleteResult);
}
/**
* {@inheritDoc}
*/
@Override()
public void logExtendedRequest(
@NotNull final LDAPConnectionInfo connectionInfo,
final int messageID,
@NotNull final ExtendedRequest extendedRequest)
{
if (logRequests && operationTypes.contains(OperationType.EXTENDED))
{
final JSONBuffer buffer = startLogMessage("request",
OperationType.EXTENDED, connectionInfo, messageID);
buffer.appendString("oid", extendedRequest.getOID());
buffer.appendBoolean("has-value", (extendedRequest.getValue() != null));
appendControls(buffer, "control-oids", extendedRequest.getControls());
logMessage(buffer, flushAfterRequestMessages);
}
}
/**
* {@inheritDoc}
*/
@Override()
public void logExtendedResult(
@NotNull final LDAPConnectionInfo connectionInfo,
final int requestMessageID,
@NotNull final ExtendedResult extendedResult)
{
logLDAPResult(connectionInfo, OperationType.EXTENDED, requestMessageID,
extendedResult);
}
/**
* {@inheritDoc}
*/
@Override()
public void logModifyRequest(@NotNull final LDAPConnectionInfo connectionInfo,
final int messageID,
@NotNull final ReadOnlyModifyRequest modifyRequest)
{
if (logRequests && operationTypes.contains(OperationType.MODIFY))
{
final JSONBuffer buffer = startLogMessage("request",
OperationType.MODIFY, connectionInfo, messageID);
appendDN(buffer, "dn", modifyRequest.getDN());
if (includeModifyAttributeNames)
{
final List mods = modifyRequest.getModifications();
if (includeModifyAttributeValues)
{
buffer.beginArray("modifications");
for (final Modification m : mods)
{
buffer.beginObject();
final String name = m.getAttributeName();
buffer.appendString("attribute-name", name);
buffer.appendString("modification-type",
m.getModificationType().getName());
buffer.beginArray("attribute-values");
final String baseName =
StaticUtils.toLowerCase(Attribute.getBaseName(name));
if (fullAttributesToRedact.contains(baseName))
{
for (final String value : m.getValues())
{
buffer.appendString(REDACTED_VALUE_STRING);
}
}
else
{
for (final String value : m.getValues())
{
buffer.appendString(value);
}
}
buffer.endArray();
buffer.endObject();
}
buffer.endArray();
}
else
{
final Map modifiedAttributes = new LinkedHashMap<>(
StaticUtils.computeMapCapacity(mods.size()));
for (final Modification m : modifyRequest.getModifications())
{
final String name = m.getAttributeName();
final String lowerName = StaticUtils.toLowerCase(name);
if (! modifiedAttributes.containsKey(lowerName))
{
modifiedAttributes.put(lowerName, name);
}
}
buffer.beginArray("modified-attributes");
for (final String attributeName : modifiedAttributes.values())
{
buffer.appendString(attributeName);
}
buffer.endArray();
}
}
appendControls(buffer, "control-oids", modifyRequest.getControls());
logMessage(buffer, flushAfterRequestMessages);
}
}
/**
* {@inheritDoc}
*/
@Override()
public void logModifyResult(@NotNull final LDAPConnectionInfo connectionInfo,
final int requestMessageID,
@NotNull final LDAPResult modifyResult)
{
logLDAPResult(connectionInfo, OperationType.MODIFY, requestMessageID,
modifyResult);
}
/**
* {@inheritDoc}
*/
@Override()
public void logModifyDNRequest(
@NotNull final LDAPConnectionInfo connectionInfo,
final int messageID,
@NotNull final ReadOnlyModifyDNRequest modifyDNRequest)
{
if (logRequests && operationTypes.contains(OperationType.MODIFY_DN))
{
final JSONBuffer buffer = startLogMessage("request",
OperationType.MODIFY_DN, connectionInfo, messageID);
appendDN(buffer, "dn", modifyDNRequest.getDN());
appendDN(buffer, "new-rdn", modifyDNRequest.getNewRDN());
buffer.appendBoolean("delete-old-rdn", modifyDNRequest.deleteOldRDN());
final String newSuperiorDN = modifyDNRequest.getNewSuperiorDN();
if (newSuperiorDN != null)
{
appendDN(buffer, "new-superior-dn", newSuperiorDN);
}
appendControls(buffer, "control-oids", modifyDNRequest.getControls());
logMessage(buffer, flushAfterRequestMessages);
}
}
/**
* {@inheritDoc}
*/
@Override()
public void logModifyDNResult(
@NotNull final LDAPConnectionInfo connectionInfo,
final int requestMessageID,
@NotNull final LDAPResult modifyDNResult)
{
logLDAPResult(connectionInfo, OperationType.MODIFY_DN, requestMessageID,
modifyDNResult);
}
/**
* {@inheritDoc}
*/
@Override()
public void logSearchRequest(@NotNull final LDAPConnectionInfo connectionInfo,
final int messageID,
@NotNull final ReadOnlySearchRequest searchRequest)
{
if (logRequests && operationTypes.contains(OperationType.SEARCH))
{
final JSONBuffer buffer = startLogMessage("request",
OperationType.SEARCH, connectionInfo, messageID);
appendDN(buffer, "base-dn", searchRequest.getBaseDN());
buffer.appendString("scope", searchRequest.getScope().getName());
buffer.appendString("dereference-policy",
searchRequest.getDereferencePolicy().getName());
buffer.appendNumber("size-limit", searchRequest.getSizeLimit());
buffer.appendNumber("time-limit-seconds",
searchRequest.getTimeLimitSeconds());
buffer.appendBoolean("types-only", searchRequest.typesOnly());
buffer.appendString("filter",
redactFilter(searchRequest.getFilter()).toString());
buffer.beginArray("requested-attributes");
for (final String attributeName : searchRequest.getAttributeList())
{
buffer.appendString(attributeName);
}
buffer.endArray();
appendControls(buffer, "control-oids", searchRequest.getControls());
logMessage(buffer, flushAfterRequestMessages);
}
}
/**
* {@inheritDoc}
*/
@Override()
public void logSearchEntry(@NotNull final LDAPConnectionInfo connectionInfo,
final int requestMessageID,
@NotNull final SearchResultEntry searchEntry)
{
if (logSearchEntries && operationTypes.contains(OperationType.SEARCH))
{
final JSONBuffer buffer = startLogMessage("search-entry",
OperationType.SEARCH, connectionInfo, requestMessageID);
appendDN(buffer, "dn", searchEntry.getDN());
if (includeSearchEntryAttributeNames)
{
appendAttributes(buffer, "attributes",
new ArrayList<>(searchEntry.getAttributes()),
includeSearchEntryAttributeValues);
}
appendControls(buffer, "control-oids", searchEntry.getControls());
logMessage(buffer, flushAfterRequestMessages);
}
}
/**
* {@inheritDoc}
*/
@Override()
public void logSearchReference(
@NotNull final LDAPConnectionInfo connectionInfo,
final int requestMessageID,
@NotNull final SearchResultReference searchReference)
{
if (logSearchReferences && operationTypes.contains(OperationType.SEARCH))
{
final JSONBuffer buffer = startLogMessage("search-reference",
OperationType.SEARCH, connectionInfo, requestMessageID);
buffer.beginArray("referral-urls");
for (final String url : searchReference.getReferralURLs())
{
buffer.appendString(url);
}
buffer.endArray();
appendControls(buffer, "control-oids", searchReference.getControls());
logMessage(buffer, flushAfterRequestMessages);
}
}
/**
* {@inheritDoc}
*/
@Override()
public void logSearchResult(@NotNull final LDAPConnectionInfo connectionInfo,
final int requestMessageID,
@NotNull final SearchResult searchResult)
{
logLDAPResult(connectionInfo, OperationType.SEARCH, requestMessageID,
searchResult);
}
/**
* {@inheritDoc}
*/
@Override()
public void logUnbindRequest(@NotNull final LDAPConnectionInfo connectionInfo,
final int messageID,
@NotNull final List requestControls)
{
if (logRequests && operationTypes.contains(OperationType.UNBIND))
{
final JSONBuffer buffer = startLogMessage("request",
OperationType.UNBIND, connectionInfo, messageID);
appendControls(buffer, "control-oids", requestControls);
logMessage(buffer, flushAfterRequestMessages);
}
}
/**
* {@inheritDoc}
*/
@Override()
public void logIntermediateResponse(
@NotNull final LDAPConnectionInfo connectionInfo,
final int messageID,
@NotNull final IntermediateResponse intermediateResponse)
{
if (logIntermediateResponses)
{
final JSONBuffer buffer = startLogMessage("intermediate-response", null,
connectionInfo, messageID);
final String oid = intermediateResponse.getOID();
if (oid != null)
{
buffer.appendString("oid", oid);
}
buffer.appendBoolean("has-value",
(intermediateResponse.getValue() != null));
appendControls(buffer, "control-oids",
intermediateResponse.getControls());
logMessage(buffer, flushAfterRequestMessages);
}
}
/**
* Starts generating a log message.
*
* @param messageType The message type for the log message. It must not
* be {@code null}.
* @param operationType The operation type for the log message. It may be
* {@code null} if there is no associated operation
* type.
* @param connectionInfo Information about the connection with which the
* message is associated. It must not be
* {@code null}.
* @param messageID The LDAP message ID for the associated operation.
* This will be ignored if the value is less than
* zero.
*
* @return A JSON buffer that may be used to construct the remainder of the
* log message.
*/
@NotNull()
private JSONBuffer startLogMessage(@NotNull final String messageType,
@Nullable final OperationType operationType,
@NotNull final LDAPConnectionInfo connectionInfo,
final int messageID)
{
JSONBuffer buffer = jsonBuffers.get();
if (buffer == null)
{
buffer = new JSONBuffer();
jsonBuffers.set(buffer);
}
else
{
buffer.clear();
}
buffer.beginObject();
SimpleDateFormat timestampFormatter = timestampFormatters.get();
if (timestampFormatter == null)
{
timestampFormatter =
new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSS'Z'");
timestampFormatter.setTimeZone(StaticUtils.getUTCTimeZone());
timestampFormatters.set(timestampFormatter);
}
buffer.appendString("timestamp", timestampFormatter.format(new Date()));
buffer.appendString("message-type", messageType);
if (operationType != null)
{
switch (operationType)
{
case ABANDON:
buffer.appendString("operation-type", "abandon");
break;
case ADD:
buffer.appendString("operation-type", "add");
break;
case BIND:
buffer.appendString("operation-type", "bind");
break;
case COMPARE:
buffer.appendString("operation-type", "compare");
break;
case DELETE:
buffer.appendString("operation-type", "delete");
break;
case EXTENDED:
buffer.appendString("operation-type", "extended");
break;
case MODIFY:
buffer.appendString("operation-type", "modify");
break;
case MODIFY_DN:
buffer.appendString("operation-type", "modify-dn");
break;
case SEARCH:
buffer.appendString("operation-type", "search");
break;
case UNBIND:
buffer.appendString("operation-type", "unbind");
break;
}
}
buffer.appendNumber("connection-id", connectionInfo.getConnectionID());
final String connectionName = connectionInfo.getConnectionName();
if (connectionName != null)
{
buffer.appendString("connection-name", connectionName);
}
final String connectionPoolName = connectionInfo.getConnectionPoolName();
if (connectionPoolName != null)
{
buffer.appendString("connection-pool-name", connectionPoolName);
}
if (messageID >= 0)
{
buffer.appendNumber("ldap-message-id", messageID);
}
return buffer;
}
/**
* Appends information about an exception to the provided buffer.
*
* @param buffer The buffer to which the exception should be appended.
* It must not be {@code null}.
* @param fieldName The name of the field to use for the exception
* object that is appended to the buffer. It must not be
* {@code null}.
* @param exception The exception to be appended. It must not be
* {@code null}.
*/
private void appendException(@NotNull final JSONBuffer buffer,
@NotNull final String fieldName,
@NotNull final Throwable exception)
{
buffer.beginObject(fieldName);
buffer.appendString("exception-class", exception.getClass().getName());
final String message = exception.getMessage();
if (message != null)
{
buffer.appendString("message", message);
}
buffer.beginArray("stack-trace-frames");
for (final StackTraceElement frame : exception.getStackTrace())
{
buffer.beginObject();
buffer.appendString("class", frame.getClassName());
buffer.appendString("method", frame.getMethodName());
final String fileName = frame.getFileName();
if (fileName != null)
{
buffer.appendString("file", fileName);
}
if (frame.isNativeMethod())
{
buffer.appendBoolean("is-native-method", true);
}
else
{
final int lineNumber = frame.getLineNumber();
if (lineNumber > 0)
{
buffer.appendNumber("line-number", lineNumber);
}
}
buffer.endObject();
}
buffer.endArray();
final Throwable cause = exception.getCause();
if (cause != null)
{
appendException(buffer, "caused-by", cause);
}
buffer.endObject();
}
/**
* Appends information about the given set of controls to the provided buffer,
* if control OIDs should be included in log messages.
*
* @param buffer The buffer to which the information should be appended.
* It must not be {@code null}.
* @param fieldName The name to use for the JSON field. It must not be
* {@code null}.
* @param controls The controls to be appended. It must not be
* {@code null} but may be empty.
*/
private void appendControls(@NotNull final JSONBuffer buffer,
@NotNull final String fieldName,
@NotNull final Control... controls)
{
if (includeControlOIDs && (controls.length > 0))
{
buffer.beginArray(fieldName);
for (final Control c : controls)
{
buffer.appendString(c.getOID());
}
buffer.endArray();
}
}
/**
* Appends information about the given set of controls to the provided buffer,
* if control OIDs should be included in log messages.
*
* @param buffer The buffer to which the information should be appended.
* It must not be {@code null}.
* @param fieldName The name to use for the JSON field. It must not be
* {@code null}.
* @param controls The controls to be appended. It must not be
* {@code null} but may be empty.
*/
private void appendControls(@NotNull final JSONBuffer buffer,
@NotNull final String fieldName,
@NotNull final List controls)
{
if (includeControlOIDs && (! controls.isEmpty()))
{
buffer.beginArray(fieldName);
for (final Control c : controls)
{
buffer.appendString(c.getOID());
}
buffer.endArray();
}
}
/**
* Appends a DN to the provided buffer, redacting any attribute values as
* appropriate.
*
* @param buffer The buffer to which the information should be appended.
* It must not be {@code null}.
* @param fieldName The name to use for the JSON field. It must not be
* {@code null}.
* @param dn The DN to be appended. It must not be {@code null} but
* may be empty.
*/
private void appendDN(@NotNull final JSONBuffer buffer,
@NotNull final String fieldName,
@NotNull final String dn)
{
if (fullAttributesToRedact.isEmpty())
{
buffer.appendString(fieldName, dn);
return;
}
final DN parsedDN;
try
{
parsedDN = new DN(dn);
}
catch (final Exception e)
{
Debug.debugException(e);
buffer.appendString(fieldName, dn);
return;
}
boolean redactionNeeded = false;
final RDN[] originalRDNs = parsedDN.getRDNs();
for (final RDN rdn : originalRDNs)
{
for (final String attributeName : rdn.getAttributeNames())
{
if (fullAttributesToRedact.contains(
StaticUtils.toLowerCase(attributeName)))
{
redactionNeeded = true;
break;
}
}
}
if (redactionNeeded)
{
final RDN[] newRDNs = new RDN[originalRDNs.length];
for (int i=0; i < originalRDNs.length; i++)
{
final RDN rdn = originalRDNs[i];
final String[] names = rdn.getAttributeNames();
final byte[][] values = new byte[names.length][];
for (int j=0; j < names.length; j++)
{
final String lowerName = StaticUtils.toLowerCase(names[j]);
if (fullAttributesToRedact.contains(lowerName))
{
values[j] = REDACTED_VALUE_BYTES;
}
else
{
values[j] = rdn.getByteArrayAttributeValues()[j];
}
}
newRDNs[i] = new RDN(names, values, rdn.getSchema());
}
buffer.appendString(fieldName, new DN(newRDNs).toString());
}
else
{
buffer.appendString(fieldName, dn);
}
}
/**
* Appends the given list of attributes to the provided buffer, redacting any
* values as appropriate.
*
* @param buffer The buffer to which the information should be
* appended. It must not be {@code null}.
* @param fieldName The name of the field to use for the attribute
* array. It must not be {@code null}.
* @param attributes The attributes to be appended. It must not be
* {@code null}, but may be empty.
* @param includeValues Indicates whether to include the values of the
* attributes.
*/
private void appendAttributes(@NotNull final JSONBuffer buffer,
@NotNull final String fieldName,
@NotNull final List attributes,
final boolean includeValues)
{
buffer.beginArray(fieldName);
for (final Attribute attribute : attributes)
{
if (includeValues)
{
buffer.beginObject();
buffer.appendString("name", attribute.getName());
buffer.beginArray("values");
final String baseName =
StaticUtils.toLowerCase(attribute.getBaseName());
if (fullAttributesToRedact.contains(baseName))
{
for (final String value : attribute.getValues())
{
buffer.appendString(REDACTED_VALUE_STRING);
}
}
else
{
for (final String value : attribute.getValues())
{
buffer.appendString(value);
}
}
buffer.endArray();
buffer.endObject();
}
else
{
buffer.appendString(attribute.getName());
}
}
buffer.endArray();
}
/**
* Redacts the provided filter, if necessary.
*
* @param filter The filter to be redacted. It must not be {@code null}.
*
* @return The redacted filter.
*/
@NotNull()
private Filter redactFilter(@NotNull final Filter filter)
{
switch (filter.getFilterType())
{
case Filter.FILTER_TYPE_AND:
final Filter[] currentANDComps = filter.getComponents();
final Filter[] newANDComps = new Filter[currentANDComps.length];
for (int i=0; i < currentANDComps.length; i++)
{
newANDComps[i] = redactFilter(currentANDComps[i]);
}
return Filter.createANDFilter(newANDComps);
case Filter.FILTER_TYPE_OR:
final Filter[] currentORComps = filter.getComponents();
final Filter[] newORComps = new Filter[currentORComps.length];
for (int i=0; i < currentORComps.length; i++)
{
newORComps[i] = redactFilter(currentORComps[i]);
}
return Filter.createORFilter(newORComps);
case Filter.FILTER_TYPE_NOT:
return Filter.createNOTFilter(redactFilter(filter.getNOTComponent()));
case Filter.FILTER_TYPE_EQUALITY:
return Filter.createEqualityFilter(filter.getAttributeName(),
redactAssertionValue(filter));
case Filter.FILTER_TYPE_GREATER_OR_EQUAL:
return Filter.createGreaterOrEqualFilter(filter.getAttributeName(),
redactAssertionValue(filter));
case Filter.FILTER_TYPE_LESS_OR_EQUAL:
return Filter.createLessOrEqualFilter(filter.getAttributeName(),
redactAssertionValue(filter));
case Filter.FILTER_TYPE_APPROXIMATE_MATCH:
return Filter.createApproximateMatchFilter(filter.getAttributeName(),
redactAssertionValue(filter));
case Filter.FILTER_TYPE_EXTENSIBLE_MATCH:
return Filter.createExtensibleMatchFilter(filter.getAttributeName(),
filter.getMatchingRuleID(), filter.getDNAttributes(),
redactAssertionValue(filter));
case Filter.FILTER_TYPE_SUBSTRING:
final String baseName = StaticUtils.toLowerCase(Attribute.getBaseName(
filter.getAttributeName()));
if (fullAttributesToRedact.contains(baseName))
{
final String[] redactedSubAnyStrings =
new String[filter.getSubAnyStrings().length];
Arrays.fill(redactedSubAnyStrings, REDACTED_VALUE_STRING);
return Filter.createSubstringFilter(filter.getAttributeName(),
filter.getSubInitialString() == null
? null
: REDACTED_VALUE_STRING,
redactedSubAnyStrings,
filter.getSubFinalString() == null
? null
: REDACTED_VALUE_STRING);
}
else
{
return Filter.createSubstringFilter(filter.getAttributeName(),
filter.getSubInitialString(), filter.getSubAnyStrings(),
filter.getSubFinalString());
}
case Filter.FILTER_TYPE_PRESENCE:
default:
return filter;
}
}
/**
* Retrieves an assertion value to use for a redacted filter.
*
* @param filter The filter for which to obtain the assertion value.
*
* @return The assertion value to use for a redacted filter.
*/
@NotNull()
private String redactAssertionValue(@NotNull final Filter filter)
{
final String attributeName = filter.getAttributeName();
if (attributeName == null)
{
return filter.getAssertionValue();
}
final String baseName =
StaticUtils.toLowerCase(Attribute.getBaseName(attributeName));
if (fullAttributesToRedact.contains(baseName))
{
return REDACTED_VALUE_STRING;
}
else
{
return filter.getAssertionValue();
}
}
/**
* Logs a final result message for the provided result. If the result is a
* {@code BindResult}, an {@code ExtendedResult}, or a {@code SearchResult},
* then additional information about that type of result may also be included.
*
* @param connectionInfo Information about the connection with which the
* result is associated. It must not be
* {@code null}.
* @param operationType The operation type for the log message. It must
* not be {@code null}.
* @param messageID The LDAP message ID for the associated operation.
* @param result The result to be logged.
*/
private void logLDAPResult(@NotNull final LDAPConnectionInfo connectionInfo,
@NotNull final OperationType operationType,
final int messageID,
@NotNull final LDAPResult result)
{
if (logFinalResults && operationTypes.contains(operationType))
{
final JSONBuffer buffer = startLogMessage("result", operationType,
connectionInfo, messageID);
buffer.appendNumber("result-code-value",
result.getResultCode().intValue());
buffer.appendString("result-code-name", result.getResultCode().getName());
final String diagnosticMessage = result.getDiagnosticMessage();
if (diagnosticMessage != null)
{
buffer.appendString("diagnostic-message", diagnosticMessage);
}
final String matchedDN = result.getMatchedDN();
if (matchedDN != null)
{
buffer.appendString("matched-dn", matchedDN);
}
final String[] referralURLs = result.getReferralURLs();
if ((referralURLs != null) && (referralURLs.length > 0))
{
buffer.beginArray("referral-urls");
for (final String url : referralURLs)
{
buffer.appendString(url);
}
buffer.endArray();
}
if (result instanceof BindResult)
{
final BindResult bindResult = (BindResult) result;
if (bindResult.getServerSASLCredentials() != null)
{
buffer.appendBoolean("has-server-sasl-credentials", true);
}
}
else if (result instanceof ExtendedResult)
{
final ExtendedResult extendedResult = (ExtendedResult) result;
final String oid = extendedResult.getOID();
if (oid != null)
{
buffer.appendString("oid", oid);
}
buffer.appendBoolean("has-value", (extendedResult.getValue() != null));
}
appendControls(buffer, "control-oids", result.getResponseControls());
logMessage(buffer, flushAfterFinalResultMessages);
}
}
/**
* Finalizes the message and writes it to the log handler, optionally flushing
* the handler after the message has been written.
*
* @param buffer The buffer containing the message to be written.
* @param flushHandler Indicates whether to flush the handler after the
* message has been written.
*/
private void logMessage(@NotNull final JSONBuffer buffer,
final boolean flushHandler)
{
buffer.endObject();
logHandler.publish(new LogRecord(Level.INFO, buffer.toString()));
if (flushHandler)
{
logHandler.flush();
}
}
}