/*
|
* CDDL HEADER START
|
*
|
* The contents of this file are subject to the terms of the
|
* Common Development and Distribution License, Version 1.0 only
|
* (the "License"). You may not use this file except in compliance
|
* with the License.
|
*
|
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
|
* or http://forgerock.org/license/CDDLv1.0.html.
|
* See the License for the specific language governing permissions
|
* and limitations under the License.
|
*
|
* When distributing Covered Code, include this CDDL HEADER in each
|
* file and include the License file at legal-notices/CDDLv1_0.txt.
|
* If applicable, add the following below this CDDL HEADER, with the
|
* fields enclosed by brackets "[]" replaced with your own identifying
|
* information:
|
* Portions Copyright [yyyy] [name of copyright owner]
|
*
|
* CDDL HEADER END
|
*
|
*
|
* Copyright 2015 ForgeRock AS
|
*/
|
package org.opends.server.loggers;
|
|
import static org.opends.messages.LoggerMessages.*;
|
import static org.forgerock.json.JsonValue.json;
|
import static org.forgerock.json.resource.Requests.newCreateRequest;
|
import static org.forgerock.json.resource.ResourcePath.resourcePath;
|
import static org.opends.server.loggers.CommonAudit.DEFAULT_TRANSACTION_ID;
|
import static org.opends.server.loggers.OpenDJAccessAuditEventBuilder.openDJAccessEvent;
|
import static org.opends.server.types.AuthenticationType.SASL;
|
|
import java.util.List;
|
import java.util.concurrent.TimeUnit;
|
|
import org.forgerock.audit.events.AccessAuditEventBuilder.ResponseStatus;
|
import org.forgerock.audit.events.AuditEvent;
|
import org.forgerock.i18n.LocalizableMessage;
|
import org.forgerock.i18n.LocalizableMessageBuilder;
|
import org.forgerock.i18n.slf4j.LocalizedLogger;
|
import org.forgerock.json.resource.CreateRequest;
|
import org.forgerock.json.resource.RequestHandler;
|
import org.forgerock.json.resource.ResourceException;
|
import org.forgerock.opendj.config.server.ConfigException;
|
import org.forgerock.opendj.ldap.ResultCode;
|
import org.forgerock.services.context.RootContext;
|
import org.forgerock.util.Pair;
|
import org.forgerock.util.promise.ExceptionHandler;
|
import org.forgerock.util.promise.RuntimeExceptionHandler;
|
import org.opends.server.admin.std.server.AccessLogPublisherCfg;
|
import org.opends.server.api.ClientConnection;
|
import org.opends.server.api.ExtendedOperationHandler;
|
import org.opends.server.controls.TransactionIdControl;
|
import org.opends.server.core.AbandonOperation;
|
import org.opends.server.core.AddOperation;
|
import org.opends.server.core.BindOperation;
|
import org.opends.server.core.CompareOperation;
|
import org.opends.server.core.DeleteOperation;
|
import org.opends.server.core.DirectoryServer;
|
import org.opends.server.core.ExtendedOperation;
|
import org.opends.server.core.ModifyDNOperation;
|
import org.opends.server.core.ModifyOperation;
|
import org.opends.server.core.SearchOperation;
|
import org.opends.server.core.ServerContext;
|
import org.opends.server.core.UnbindOperation;
|
import org.opends.server.types.AuthenticationInfo;
|
import org.opends.server.types.DN;
|
import org.opends.server.types.DirectoryException;
|
import org.opends.server.types.DisconnectReason;
|
import org.opends.server.types.InitializationException;
|
import org.opends.server.types.Operation;
|
import org.opends.server.util.StaticUtils;
|
|
/**
|
* Publishes access events to Common Audit.
|
*
|
* @param <T> the type of configuration
|
*/
|
abstract class CommonAuditAccessLogPublisher<T extends AccessLogPublisherCfg>
|
extends AbstractTextAccessLogPublisher<T>
|
implements CommonAuditLogPublisher
|
{
|
|
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
|
|
/** Audit service handler. */
|
private RequestHandler requestHandler;
|
|
/** Current configuration for this publisher. */
|
private T config;
|
|
private ServerContext serverContext;
|
|
@Override
|
public void setRequestHandler(RequestHandler handler)
|
{
|
this.requestHandler = handler;
|
}
|
|
abstract boolean shouldLogControlOids();
|
|
T getConfig()
|
{
|
return config;
|
}
|
|
void setConfig(T config)
|
{
|
this.config = config;
|
}
|
|
@Override
|
public void initializeLogPublisher(final T cfg, ServerContext serverContext)
|
throws ConfigException, InitializationException
|
{
|
this.serverContext = serverContext;
|
initializeFilters(cfg);
|
config = cfg;
|
}
|
|
@Override
|
public boolean isConfigurationAcceptable(final T configuration, final List<LocalizableMessage> unacceptableReasons)
|
{
|
return isFilterConfigurationAcceptable(configuration, unacceptableReasons);
|
}
|
|
@Override
|
public void logAbandonResult(final AbandonOperation abandonOperation)
|
{
|
if (!isResponseLoggable(abandonOperation))
|
{
|
return;
|
}
|
OpenDJAccessAuditEventBuilder<?> builder = getEventBuilder(abandonOperation, "ABANDON");
|
appendAbandonRequest(abandonOperation, builder);
|
appendResultCodeAndMessage(abandonOperation, builder);
|
|
sendEvent(builder.toEvent());
|
}
|
|
@Override
|
public void logAddResponse(final AddOperation addOperation)
|
{
|
if (!isResponseLoggable(addOperation))
|
{
|
return;
|
}
|
OpenDJAccessAuditEventBuilder<?> builder = getEventBuilder(addOperation, "ADD");
|
appendAddRequest(addOperation, builder);
|
appendResultCodeAndMessage(addOperation, builder);
|
DN proxiedAuthorizationDN = addOperation.getProxiedAuthorizationDN();
|
appendProxiedAuthorizationDNIfNeeded(builder, proxiedAuthorizationDN);
|
|
sendEvent(builder.toEvent());
|
}
|
|
@Override
|
public void logBindResponse(final BindOperation bindOperation)
|
{
|
if (!isResponseLoggable(bindOperation))
|
{
|
return;
|
}
|
|
OpenDJAccessAuditEventBuilder<?> builder = getEventBuilder(bindOperation, "BIND");
|
appendBindRequest(bindOperation, builder);
|
appendResultCodeAndMessage(bindOperation, builder);
|
|
final LocalizableMessage failureMessage = bindOperation.getAuthFailureReason();
|
if (failureMessage != null)
|
{
|
// this code path is mutually exclusive with the if result code is success
|
// down below
|
builder.ldapFailureMessage(failureMessage.toString());
|
if (bindOperation.getSASLMechanism() != null && bindOperation.getSASLAuthUserEntry() != null)
|
{ // SASL bind and we have successfully found a user entry for auth
|
builder.userId(bindOperation.getSASLAuthUserEntry().getName().toString());
|
}
|
else
|
{ // SASL bind failed to find user entry for auth or simple bind
|
builder.userId(bindOperation.getRawBindDN().toString());
|
}
|
}
|
|
if (bindOperation.getResultCode() == ResultCode.SUCCESS)
|
{
|
// this code path is mutually exclusive with the if failure message exist
|
// just above
|
final AuthenticationInfo authInfo = bindOperation.getAuthenticationInfo();
|
if (authInfo != null)
|
{
|
final DN authDN = authInfo.getAuthenticationDN();
|
if (authDN != null)
|
{
|
builder.userId(authDN.toString());
|
|
final DN authzDN = authInfo.getAuthorizationDN();
|
if (!authDN.equals(authzDN))
|
{
|
builder.runAs(authzDN.toString());
|
}
|
}
|
else
|
{
|
builder.userId("");
|
}
|
}
|
}
|
|
sendEvent(builder.toEvent());
|
}
|
|
@Override
|
public void logCompareResponse(final CompareOperation compareOperation)
|
{
|
if (!isResponseLoggable(compareOperation))
|
{
|
return;
|
}
|
OpenDJAccessAuditEventBuilder<?> builder = getEventBuilder(compareOperation, "COMPARE");
|
appendCompareRequest(compareOperation, builder);
|
appendResultCodeAndMessage(compareOperation, builder);
|
DN proxiedAuthorizationDN = compareOperation.getProxiedAuthorizationDN();
|
appendProxiedAuthorizationDNIfNeeded(builder, proxiedAuthorizationDN);
|
|
sendEvent(builder.toEvent());
|
}
|
|
private void appendProxiedAuthorizationDNIfNeeded(OpenDJAccessAuditEventBuilder<?> builder, DN proxiedAuthorizationDN)
|
{
|
if (proxiedAuthorizationDN != null)
|
{
|
builder.runAs(proxiedAuthorizationDN.toString());
|
}
|
}
|
|
@Override
|
public void logConnect(final ClientConnection clientConnection)
|
{
|
if (!isConnectLoggable(clientConnection))
|
{
|
return;
|
}
|
OpenDJAccessAuditEventBuilder<?> builder = openDJAccessEvent()
|
.eventName("DJ-" + clientConnection.getProtocol() + "-" + "CONNECT")
|
.client(clientConnection.getClientAddress(), clientConnection.getClientPort())
|
.server(clientConnection.getServerAddress(), clientConnection.getServerPort())
|
.request(clientConnection.getProtocol(), "CONNECT")
|
.transactionId(CommonAudit.DEFAULT_TRANSACTION_ID)
|
.response(ResponseStatus.SUCCESSFUL, String.valueOf(ResultCode.SUCCESS.intValue()), 0, TimeUnit.MILLISECONDS)
|
.ldapConnectionId(clientConnection.getConnectionID());
|
|
sendEvent(builder.toEvent());
|
}
|
|
@Override
|
public void logDeleteResponse(final DeleteOperation deleteOperation)
|
{
|
if (!isResponseLoggable(deleteOperation))
|
{
|
return;
|
}
|
OpenDJAccessAuditEventBuilder<?> builder = getEventBuilder(deleteOperation, "DELETE");
|
appendDeleteRequest(deleteOperation, builder);
|
appendResultCodeAndMessage(deleteOperation, builder);
|
DN proxiedAuthorizationDN = deleteOperation.getProxiedAuthorizationDN();
|
appendProxiedAuthorizationDNIfNeeded(builder, proxiedAuthorizationDN);
|
|
sendEvent(builder.toEvent());
|
}
|
|
@Override
|
public void logDisconnect(final ClientConnection clientConnection, final DisconnectReason disconnectReason,
|
final LocalizableMessage message)
|
{
|
if (!isDisconnectLoggable(clientConnection))
|
{
|
return;
|
}
|
OpenDJAccessAuditEventBuilder<?> builder = openDJAccessEvent()
|
.eventName("DJ-" + clientConnection.getProtocol() + "-" + "DISCONNECT")
|
.client(clientConnection.getClientAddress(), clientConnection.getClientPort())
|
.server(clientConnection.getServerAddress(), clientConnection.getServerPort())
|
.request(clientConnection.getProtocol(), "DISCONNECT")
|
.transactionId(CommonAudit.DEFAULT_TRANSACTION_ID)
|
.response(ResponseStatus.SUCCESSFUL, String.valueOf(ResultCode.SUCCESS.intValue()), 0, TimeUnit.MILLISECONDS)
|
.ldapConnectionId(clientConnection.getConnectionID())
|
.ldapReason(disconnectReason)
|
.ldapMessage(message);
|
|
sendEvent(builder.toEvent());
|
}
|
|
@Override
|
public void logExtendedResponse(final ExtendedOperation extendedOperation)
|
{
|
if (!isResponseLoggable(extendedOperation))
|
{
|
return;
|
}
|
OpenDJAccessAuditEventBuilder<?> builder = getEventBuilder(extendedOperation, "EXTENDED");
|
appendExtendedRequest(extendedOperation, builder);
|
appendResultCodeAndMessage(extendedOperation, builder);
|
final String oid = extendedOperation.getResponseOID();
|
if (oid != null)
|
{
|
final ExtendedOperationHandler<?> extOpHandler = DirectoryServer.getExtendedOperationHandler(oid);
|
if (extOpHandler != null)
|
{
|
String name = extOpHandler.getExtendedOperationName();
|
builder.ldapName(name);
|
}
|
builder.ldapOid(oid);
|
}
|
sendEvent(builder.toEvent());
|
}
|
|
@Override
|
public void logModifyDNResponse(final ModifyDNOperation modifyDNOperation)
|
{
|
if (!isResponseLoggable(modifyDNOperation))
|
{
|
return;
|
}
|
OpenDJAccessAuditEventBuilder<?> builder = getEventBuilder(modifyDNOperation, "MODIFYDN");
|
appendModifyDNRequest(modifyDNOperation, builder);
|
appendResultCodeAndMessage(modifyDNOperation, builder);
|
DN proxiedAuthorizationDN = modifyDNOperation.getProxiedAuthorizationDN();
|
appendProxiedAuthorizationDNIfNeeded(builder, proxiedAuthorizationDN);
|
|
sendEvent(builder.toEvent());
|
}
|
|
@Override
|
public void logModifyResponse(final ModifyOperation modifyOperation)
|
{
|
if (!isResponseLoggable(modifyOperation))
|
{
|
return;
|
}
|
OpenDJAccessAuditEventBuilder<?> builder = getEventBuilder(modifyOperation, "MODIFY");
|
appendModifyRequest(modifyOperation, builder);
|
appendResultCodeAndMessage(modifyOperation, builder);
|
DN proxiedAuthorizationDN = modifyOperation.getProxiedAuthorizationDN();
|
appendProxiedAuthorizationDNIfNeeded(builder, proxiedAuthorizationDN);
|
|
sendEvent(builder.toEvent());
|
}
|
|
@Override
|
public void logSearchResultDone(final SearchOperation searchOperation)
|
{
|
if (!isResponseLoggable(searchOperation))
|
{
|
return;
|
}
|
OpenDJAccessAuditEventBuilder<?> builder = getEventBuilder(searchOperation, "SEARCH");
|
builder
|
.ldapSearch(searchOperation)
|
.ldapNEntries(searchOperation.getEntriesSent());
|
appendResultCodeAndMessage(searchOperation, builder);
|
DN proxiedAuthorizationDN = searchOperation.getProxiedAuthorizationDN();
|
appendProxiedAuthorizationDNIfNeeded(builder, proxiedAuthorizationDN);
|
|
sendEvent(builder.toEvent());
|
}
|
|
@Override
|
public void logUnbind(final UnbindOperation unbindOperation)
|
{
|
if (!isRequestLoggable(unbindOperation))
|
{
|
return;
|
}
|
sendEvent(getEventBuilder(unbindOperation, "UNBIND").toEvent());
|
}
|
|
@Override
|
protected void close0()
|
{
|
// nothing to do because closing is managed in the CommonAudit class
|
}
|
|
private void appendAbandonRequest(final AbandonOperation abandonOperation,
|
final OpenDJAccessAuditEventBuilder<?> builder)
|
{
|
builder.ldapIdToAbandon(abandonOperation.getIDToAbandon());
|
}
|
|
private void appendAddRequest(final AddOperation addOperation, OpenDJAccessAuditEventBuilder<?> builder)
|
{
|
builder.ldapDn(addOperation.getRawEntryDN().toString());
|
}
|
|
private void appendBindRequest(final BindOperation bindOperation, final OpenDJAccessAuditEventBuilder<?> builder)
|
{
|
builder.ldapProtocolVersion(bindOperation.getProtocolVersion());
|
final String authType = bindOperation.getAuthenticationType() != SASL ?
|
bindOperation.getAuthenticationType().toString() : "SASL mechanism=" + bindOperation.getSASLMechanism();
|
builder.ldapAuthType(authType);
|
|
builder.ldapDn(bindOperation.getRawBindDN().toString());
|
|
}
|
|
private void appendCompareRequest(final CompareOperation compareOperation,
|
final OpenDJAccessAuditEventBuilder<?> builder)
|
{
|
builder.ldapDn(compareOperation.getRawEntryDN().toString());
|
builder.ldapAttr(compareOperation.getAttributeType().getNameOrOID());
|
}
|
|
private void appendDeleteRequest(final DeleteOperation deleteOperation,
|
final OpenDJAccessAuditEventBuilder<?> builder)
|
{
|
builder.ldapDn(deleteOperation.getRawEntryDN().toString());
|
}
|
|
private void appendExtendedRequest(final ExtendedOperation extendedOperation,
|
final OpenDJAccessAuditEventBuilder<?> builder)
|
{
|
final String oid = extendedOperation.getRequestOID();
|
final ExtendedOperationHandler<?> extOpHandler = DirectoryServer.getExtendedOperationHandler(oid);
|
if (extOpHandler != null)
|
{
|
final String name = extOpHandler.getExtendedOperationName();
|
builder.ldapName(name);
|
}
|
builder.ldapOid(oid);
|
}
|
|
private void appendModifyDNRequest(final ModifyDNOperation modifyDNOperation,
|
final OpenDJAccessAuditEventBuilder<?> builder)
|
{
|
builder.ldapDn(modifyDNOperation.getRawEntryDN().toString());
|
builder.ldapModifyDN(modifyDNOperation);
|
}
|
|
private void appendModifyRequest(final ModifyOperation modifyOperation,
|
final OpenDJAccessAuditEventBuilder<?> builder)
|
{
|
builder.ldapDn(modifyOperation.getRawEntryDN().toString());
|
}
|
|
private OpenDJAccessAuditEventBuilder<?> appendResultCodeAndMessage(
|
Operation operation, OpenDJAccessAuditEventBuilder<?> builder)
|
{
|
final LocalizableMessageBuilder message = operation.getErrorMessage();
|
int resultCode = operation.getResultCode().intValue();
|
ResponseStatus status = resultCode == 0 ? ResponseStatus.SUCCESSFUL : ResponseStatus.FAILED;
|
Pair<Long, TimeUnit> executionTime = getExecutionTime(operation);
|
if (message != null && message.length() > 0)
|
{
|
builder.responseWithDetail(status, String.valueOf(resultCode), executionTime.getFirst(),
|
executionTime.getSecond(), json(message.toString()));
|
}
|
else
|
{
|
builder.response(status, String.valueOf(resultCode), executionTime.getFirst(), executionTime.getSecond());
|
}
|
builder.ldapMaskedResultAndMessage(operation);
|
return builder;
|
}
|
|
/** Returns an event builder with all common fields filled. */
|
private OpenDJAccessAuditEventBuilder<?> getEventBuilder(final Operation operation, final String opType)
|
{
|
ClientConnection clientConn = operation.getClientConnection();
|
|
OpenDJAccessAuditEventBuilder<?> builder = openDJAccessEvent()
|
.eventName("DJ-" + clientConn.getProtocol() + "-" + opType)
|
.client(clientConn.getClientAddress(), clientConn.getClientPort())
|
.server(clientConn.getServerAddress(), clientConn.getServerPort())
|
.request(clientConn.getProtocol(), opType)
|
.ldapAdditionalItems(operation)
|
.ldapSync(operation)
|
.ldapIds(operation)
|
.transactionId(getTransactionId(operation));
|
|
if (shouldLogControlOids())
|
{
|
builder.ldapControls(operation);
|
}
|
return builder;
|
}
|
|
private String getTransactionId(Operation operation)
|
{
|
String transactionId = getTransactionIdFromControl(operation);
|
if (transactionId == null || !serverContext.getCommonAudit().shouldTrustTransactionIds())
|
{
|
// use a default value
|
transactionId = DEFAULT_TRANSACTION_ID;
|
}
|
return transactionId;
|
}
|
|
private String getTransactionIdFromControl(Operation operation)
|
{
|
try
|
{
|
TransactionIdControl control = operation.getRequestControl(TransactionIdControl.DECODER);
|
return control != null ? control.getTransactionId() : null;
|
}
|
catch (DirectoryException e)
|
{
|
logger.error(ERR_COMMON_AUDIT_INVALID_TRANSACTION_ID.get(StaticUtils.stackTraceToSingleLineString(e)));
|
}
|
return null;
|
}
|
|
private Pair<Long,TimeUnit> getExecutionTime(final Operation operation)
|
{
|
Long etime = operation.getProcessingNanoTime();
|
// if not configured for nanos, use millis
|
return etime <= -1 ?
|
Pair.of(operation.getProcessingTime(), TimeUnit.MILLISECONDS) :
|
Pair.of(etime, TimeUnit.NANOSECONDS);
|
}
|
|
/** Sends an JSON-encoded event to the audit service. */
|
private void sendEvent(AuditEvent event)
|
{
|
CreateRequest request = newCreateRequest(resourcePath("/ldap-access"), event.getValue());
|
requestHandler
|
.handleCreate(new RootContext(), request)
|
.thenOnException(new ExceptionHandler<ResourceException>()
|
{
|
@Override
|
public void handleException(ResourceException e)
|
{
|
logger.error(ERR_COMMON_AUDIT_UNABLE_TO_PROCESS_LOG_EVENT.get(StaticUtils.stackTraceToSingleLineString(e)));
|
}
|
})
|
.thenOnRuntimeException(new RuntimeExceptionHandler()
|
{
|
@Override
|
public void handleRuntimeException(RuntimeException e)
|
{
|
logger.error(ERR_COMMON_AUDIT_UNABLE_TO_PROCESS_LOG_EVENT.get(StaticUtils.stackTraceToSingleLineString(e)));
|
}
|
});
|
}
|
|
}
|