/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions Copyright [year] [name of copyright owner]". * * Copyright 2015-2016 ForgeRock AS. */ package org.opends.server.loggers; import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString; import static java.util.Arrays.asList; import static org.opends.messages.LoggerMessages.*; import static org.forgerock.audit.AuditServiceBuilder.newAuditService; import static org.forgerock.audit.events.EventTopicsMetaDataBuilder.coreTopicSchemas; import static org.forgerock.audit.json.AuditJsonConfig.registerHandlerToService; import static org.opends.server.util.StaticUtils.getFileForPath; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; import org.forgerock.audit.AuditException; import org.forgerock.audit.AuditService; import org.forgerock.audit.AuditServiceBuilder; import org.forgerock.audit.AuditServiceConfiguration; import org.forgerock.audit.AuditServiceProxy; import org.forgerock.audit.DependencyProvider; import org.forgerock.audit.events.EventTopicsMetaData; import org.forgerock.audit.events.handlers.FileBasedEventHandlerConfiguration; import org.forgerock.audit.events.handlers.FileBasedEventHandlerConfiguration.FileRetention; import org.forgerock.audit.events.handlers.FileBasedEventHandlerConfiguration.FileRotation; import org.forgerock.audit.filter.FilterPolicy; import org.forgerock.audit.handlers.csv.CsvAuditEventHandler; import org.forgerock.audit.handlers.csv.CsvAuditEventHandlerConfiguration; import org.forgerock.audit.handlers.csv.CsvAuditEventHandlerConfiguration.CsvFormatting; import org.forgerock.audit.handlers.csv.CsvAuditEventHandlerConfiguration.CsvSecurity; import org.forgerock.audit.handlers.csv.CsvAuditEventHandlerConfiguration.EventBufferingConfiguration; import org.forgerock.audit.handlers.json.JsonAuditEventHandler; import org.forgerock.audit.handlers.json.JsonAuditEventHandlerConfiguration; import org.forgerock.audit.json.AuditJsonConfig; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.forgerock.json.JsonValue; import org.forgerock.json.resource.RequestHandler; import org.forgerock.opendj.config.ConfigurationFramework; import org.forgerock.opendj.config.server.ConfigException; import org.forgerock.opendj.ldap.DN; import org.forgerock.opendj.server.config.server.CsvFileAccessLogPublisherCfg; import org.forgerock.opendj.server.config.server.CsvFileHTTPAccessLogPublisherCfg; import org.forgerock.opendj.server.config.server.ExternalAccessLogPublisherCfg; import org.forgerock.opendj.server.config.server.ExternalHTTPAccessLogPublisherCfg; import org.forgerock.opendj.server.config.server.FileCountLogRetentionPolicyCfg; import org.forgerock.opendj.server.config.server.FixedTimeLogRotationPolicyCfg; import org.forgerock.opendj.server.config.server.FreeDiskSpaceLogRetentionPolicyCfg; import org.forgerock.opendj.server.config.server.JsonFileAccessLogPublisherCfg; import org.forgerock.opendj.server.config.server.JsonFileHTTPAccessLogPublisherCfg; import org.forgerock.opendj.server.config.server.LogPublisherCfg; import org.forgerock.opendj.server.config.server.LogRetentionPolicyCfg; import org.forgerock.opendj.server.config.server.LogRotationPolicyCfg; import org.forgerock.opendj.server.config.server.SizeLimitLogRetentionPolicyCfg; import org.forgerock.opendj.server.config.server.SizeLimitLogRotationPolicyCfg; import org.forgerock.opendj.server.config.server.TimeLimitLogRotationPolicyCfg; import org.opends.server.core.ServerContext; /** * Entry point for the common audit facility. *

* This class manages the AuditService instances and Audit Event Handlers that correspond to the * publishers defined in OpenDJ configuration. *

* In theory there should be only one instance of AuditService for all the event handlers but * defining one service per handler allow to perform filtering at the DJ server level. */ public class CommonAudit { /** Transaction id used when the incoming request does not contain a transaction id. */ public static final String DEFAULT_TRANSACTION_ID = "0"; private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); private static final String AUDIT_SERVICE_JSON_CONFIGURATION_FILE = "audit-config.json"; /** Dependency provider used to instantiate the handlers. */ private final DependencyProvider dependencyProvider; /** Configuration framework is used to get an up-to-date class loader with any external library available. */ private final ConfigurationFramework configurationFramework; /** Cache of audit services per configuration entry normalized name. */ private final Map auditServiceCache = new ConcurrentHashMap<>(10); /** Cache of PublisherConfig per http access configuration entry normalized name. */ private final Map httpAccessPublishers = new ConcurrentHashMap<>(5); /** Cache of PublisherConfig per access configuration entry normalized name. */ private final Map accessPublishers = new ConcurrentHashMap<>(5); /** Audit service shared by all HTTP access publishers. */ private final AuditServiceProxy httpAccessAuditService; private final AtomicBoolean trustTransactionIds = new AtomicBoolean(false); private final ServerContext serverContext; /** * Creates the common audit. * * @param serverContext * The server context. * * @throws ConfigException * If an error occurs. */ public CommonAudit(ServerContext serverContext) throws ConfigException { this.serverContext = serverContext; configurationFramework = ConfigurationFramework.getInstance(); this.dependencyProvider = new CommonAuditDependencyProvider(); this.httpAccessAuditService = createAuditServiceWithoutHandlers(); } /** * Indicates if transactionIds received from requests should be trusted. * * @return {@code true} if transactionIds should be trusted, {@code false} otherwise */ public boolean shouldTrustTransactionIds() { return trustTransactionIds.get(); } /** * Sets the indicator for transactionIds trusting. * * @param shouldTrust * {@code true} if transactionIds should be trusted, {@code false} * otherwise */ public void setTrustTransactionIds(boolean shouldTrust) { trustTransactionIds.set(shouldTrust); } private AuditServiceProxy createAuditServiceWithoutHandlers() throws ConfigException { try { return buildAuditService(new AuditServiceSetup() { @Override public void addHandlers(AuditServiceBuilder builder) { // no handler to add } }); } catch (IOException | ConfigException | AuditException e) { throw new ConfigException(ERR_COMMON_AUDIT_CREATE.get(e), e); } } /** * Returns the Common Audit request handler for the provided configuration. * * @param config * The log publisher configuration * @return the request handler associated to the log publisher * @throws ConfigException * If an error occurs */ public RequestHandler getRequestHandler(LogPublisherCfg config) throws ConfigException { if (new PublisherConfig(serverContext, config).logType.isHttp()) { return httpAccessAuditService; } return auditServiceCache.get(getConfigNormalizedName(config)); } /** * Adds or updates the publisher corresponding to the provided configuration to common audit. * * @param newConfig * Configuration of the publisher * @throws ConfigException * If an error occurs. */ public void addOrUpdatePublisher(final LogPublisherCfg newConfig) throws ConfigException { if (newConfig.isEnabled()) { logger.trace(String.format("Setting up common audit for configuration entry: %s", newConfig.dn())); try { final PublisherConfig newPublisher = new PublisherConfig(serverContext, newConfig); String normalizedName = getConfigNormalizedName(newConfig); if (newPublisher.logType.isHttp()) { // if an old version exists, it is replaced by the new one httpAccessPublishers.put(normalizedName, newPublisher); buildAuditService(httpAccessAuditServiceSetup()); } else // all other logs { final AuditServiceProxy existingService = auditServiceCache.get(normalizedName); AuditServiceProxy auditService = buildAuditService(new AuditServiceSetup(existingService) { @Override public void addHandlers(AuditServiceBuilder builder) throws ConfigException { registerHandlerName(newPublisher.getName()); addHandlerToBuilder(newPublisher, builder); } }); auditServiceCache.put(normalizedName, auditService); accessPublishers.put(normalizedName, newPublisher); } } catch (Exception e) { throw new ConfigException(ERR_COMMON_AUDIT_ADD_OR_UPDATE_LOG_PUBLISHER.get(newConfig.dn(), e), e); } } } /** * Removes the publisher corresponding to the provided configuration from common audit. * * @param config * Configuration of publisher to remove * @throws ConfigException * If an error occurs. */ public void removePublisher(LogPublisherCfg config) throws ConfigException { logger.trace(String.format("Shutting down common audit for configuration entry:", config.dn())); String normalizedName = getConfigNormalizedName(config); try { if (httpAccessPublishers.containsKey(normalizedName)) { httpAccessPublishers.remove(normalizedName); buildAuditService(httpAccessAuditServiceSetup()); } else if (accessPublishers.containsKey(normalizedName)) { accessPublishers.remove(normalizedName); AuditServiceProxy auditService = auditServiceCache.remove(normalizedName); if (auditService != null) { auditService.shutdown(); } } // else it is not a registered publisher, nothing to do } catch (Exception e) { throw new ConfigException(ERR_COMMON_AUDIT_REMOVE_LOG_PUBLISHER.get(config.dn(), e), e); } } /** Shutdown common audit. */ public void shutdown() { httpAccessAuditService.shutdown(); for (AuditServiceProxy service : auditServiceCache.values()) { service.shutdown(); } } private AuditServiceSetup httpAccessAuditServiceSetup() { return new AuditServiceSetup(httpAccessAuditService) { @Override public void addHandlers(AuditServiceBuilder builder) throws ConfigException { for (PublisherConfig publisher : httpAccessPublishers.values()) { registerHandlerName(publisher.getName()); addHandlerToBuilder(publisher, builder); } } }; } /** * Strategy for the setup of AuditService. *

* Unless no handler must be added, this class should be extended and * implementations should override the {@code addHandlers()} method. */ static abstract class AuditServiceSetup { private final AuditServiceProxy existingAuditServiceProxy; private final List names = new ArrayList<>(); /** Creation with no existing audit service. */ AuditServiceSetup() { this.existingAuditServiceProxy = null; } /** Creation with an existing audit service. */ AuditServiceSetup(AuditServiceProxy existingAuditService) { this.existingAuditServiceProxy = existingAuditService; } abstract void addHandlers(AuditServiceBuilder builder) throws ConfigException; void registerHandlerName(String name) { names.add(name); } List getHandlerNames() { return names; } boolean mustCreateAuditServiceProxy() { return existingAuditServiceProxy == null; } AuditServiceProxy getExistingAuditServiceProxy() { return existingAuditServiceProxy; } } private AuditServiceProxy buildAuditService(AuditServiceSetup setup) throws IOException, AuditException, ConfigException { final JsonValue jsonConfig; try (InputStream input = getClass().getResourceAsStream(AUDIT_SERVICE_JSON_CONFIGURATION_FILE)) { jsonConfig = AuditJsonConfig.getJson(input); } EventTopicsMetaData eventTopicsMetaData = coreTopicSchemas() .withCoreTopicSchemaExtensions(jsonConfig.get("extensions")) .withAdditionalTopicSchemas(jsonConfig.get("additionalTopics")) .build(); AuditServiceBuilder builder = newAuditService() .withEventTopicsMetaData(eventTopicsMetaData) .withDependencyProvider(dependencyProvider); setup.addHandlers(builder); AuditServiceConfiguration auditConfig = new AuditServiceConfiguration(); auditConfig.setAvailableAuditEventHandlers(setup.getHandlerNames()); auditConfig.setFilterPolicies(getFilterPoliciesToPreventHttpHeadersLogging()); builder.withConfiguration(auditConfig); AuditService audit = builder.build(); final AuditServiceProxy proxy; if (setup.mustCreateAuditServiceProxy()) { proxy = new AuditServiceProxy(audit); logger.trace("Starting up new common audit service"); proxy.startup(); } else { proxy = setup.getExistingAuditServiceProxy(); proxy.setDelegate(audit); logger.trace("Starting up existing updated common audit service"); } return proxy; } /** * Build filter policies at the AuditService level to prevent logging of the headers for HTTP requests. *

* HTTP Headers may contain authentication information. */ private Map getFilterPoliciesToPreventHttpHeadersLogging() { Map filterPolicies = new HashMap<>(); FilterPolicy policy = new FilterPolicy(); policy.setExcludeIf(asList("/http-access/http/request/headers")); filterPolicies.put("field", policy); return filterPolicies; } private void addHandlerToBuilder(PublisherConfig publisher, AuditServiceBuilder builder) throws ConfigException { switch (publisher.auditType) { case CSV: addCsvHandler(publisher, builder); break; case JSON: addJsonHandler(publisher, builder); break; case EXTERNAL: addExternalHandler(publisher, builder); break; default: throw new ConfigException(ERR_COMMON_AUDIT_UNSUPPORTED_HANDLER_TYPE.get(publisher.getDn())); } } /** Add a handler defined externally in a JSON configuration file. */ private void addExternalHandler(PublisherConfig publisher, AuditServiceBuilder builder) throws ConfigException { ExternalConfigData config = publisher.getExternalConfig(); File configFile = getFileForPath(config.getConfigurationFile()); try (InputStream input = new BufferedInputStream(new FileInputStream(configFile))) { JsonValue jsonConfig = AuditJsonConfig.getJson(input); registerHandlerToService(jsonConfig, builder, configurationFramework.getClassLoader()); } catch (IOException e) { throw new ConfigException(ERR_COMMON_AUDIT_EXTERNAL_HANDLER_JSON_FILE.get(configFile, publisher.getDn(), e), e); } catch (Exception e) { throw new ConfigException(ERR_COMMON_AUDIT_EXTERNAL_HANDLER_CREATION.get(publisher.getDn(), e), e); } } private void addCsvHandler(PublisherConfig publisher, AuditServiceBuilder builder) throws ConfigException { try { CsvConfigData config = publisher.getCsvConfig(); CsvAuditEventHandlerConfiguration csvConfig = new CsvAuditEventHandlerConfiguration(); File logDirectory = getFileForPath(config.getLogDirectory()); csvConfig.setLogDirectory(logDirectory.getAbsolutePath()); csvConfig.setName(publisher.getName()); csvConfig.setTopics(Collections.singleton(publisher.getCommonAuditTopic())); addCsvHandlerFormattingConfig(config, csvConfig); addCsvHandlerBufferingConfig(config, csvConfig); addCsvHandlerSecureConfig(publisher, config, csvConfig); addHandlerRotationConfig(publisher, config, csvConfig); addHandlerRetentionConfig(publisher, config, csvConfig); builder.withAuditEventHandler(CsvAuditEventHandler.class, csvConfig); } catch (Exception e) { throw new ConfigException(ERR_COMMON_AUDIT_FILE_BASED_HANDLER_CREATION.get(publisher.getDn(), e), e); } } private void addCsvHandlerFormattingConfig(CsvConfigData config, CsvAuditEventHandlerConfiguration auditConfig) throws ConfigException { CsvFormatting formatting = new CsvFormatting(); formatting.setQuoteChar(config.getQuoteChar()); formatting.setDelimiterChar(config.getDelimiterChar()); String endOfLineSymbols = config.getEndOfLineSymbols(); if (endOfLineSymbols != null && !endOfLineSymbols.isEmpty()) { formatting.setEndOfLineSymbols(endOfLineSymbols); } auditConfig.setFormatting(formatting); } private void addCsvHandlerBufferingConfig(CsvConfigData config, CsvAuditEventHandlerConfiguration auditConfig) { EventBufferingConfiguration bufferingConfig = new EventBufferingConfiguration(); bufferingConfig.setEnabled(config.isAsynchronous()); bufferingConfig.setAutoFlush(config.isAutoFlush()); auditConfig.setBufferingConfiguration(bufferingConfig); } private void addCsvHandlerSecureConfig(PublisherConfig publisher, CsvConfigData config, CsvAuditEventHandlerConfiguration auditConfig) { if (config.isTamperEvident()) { CsvSecurity security = new CsvSecurity(); security.setSignatureInterval(config.getSignatureTimeInterval() + "ms"); security.setEnabled(true); String keyStoreFile = config.getKeystoreFile(); security.setFilename(getFileForPath(keyStoreFile).getPath()); security.setPassword(getSecurePassword(publisher, config)); auditConfig.setSecurity(security); } } private void addJsonHandler(PublisherConfig publisher, AuditServiceBuilder builder) throws ConfigException { try { JsonConfigData config = publisher.getJsonConfig(); JsonAuditEventHandlerConfiguration jsonConfig = new JsonAuditEventHandlerConfiguration(); File logDirectory = getFileForPath(config.getLogDirectory()); jsonConfig.setLogDirectory(logDirectory.getAbsolutePath()); jsonConfig.setName(publisher.getName()); jsonConfig.setTopics(Collections.singleton(publisher.getCommonAuditTopic())); addJsonHandlerBufferingConfig(config, jsonConfig); addHandlerRetentionConfig(publisher, config, jsonConfig); addHandlerRotationConfig(publisher, config, jsonConfig); builder.withAuditEventHandler(JsonAuditEventHandler.class, jsonConfig); } catch (Exception e) { throw new ConfigException(ERR_COMMON_AUDIT_FILE_BASED_HANDLER_CREATION.get(publisher.getDn(), e), e); } } private void addJsonHandlerBufferingConfig(JsonConfigData config, JsonAuditEventHandlerConfiguration auditConfig) { JsonAuditEventHandlerConfiguration.EventBufferingConfiguration jsonBufferingConfig = new JsonAuditEventHandlerConfiguration.EventBufferingConfiguration(); } private void addHandlerRotationConfig(PublisherConfig publisher, HandlerConfigData config, FileBasedEventHandlerConfiguration auditConfig) throws ConfigException { SortedSet rotationPolicies = config.getRotationPolicies(); if (rotationPolicies.isEmpty()) { return; } FileRotation fileRotation = new FileRotation(); fileRotation.setRotationEnabled(true); for (final String policy : rotationPolicies) { LogRotationPolicyCfg policyConfig = serverContext.getRootConfig().getLogRotationPolicy(policy); if (policyConfig instanceof FixedTimeLogRotationPolicyCfg) { List times = convertTimesOfDay(publisher, (FixedTimeLogRotationPolicyCfg) policyConfig); fileRotation.setRotationTimes(times); } else if (policyConfig instanceof SizeLimitLogRotationPolicyCfg) { fileRotation.setMaxFileSize(((SizeLimitLogRotationPolicyCfg) policyConfig).getFileSizeLimit()); } else if (policyConfig instanceof TimeLimitLogRotationPolicyCfg) { long rotationInterval = ((TimeLimitLogRotationPolicyCfg) policyConfig).getRotationInterval(); fileRotation.setRotationInterval(rotationInterval + " ms"); } else { throw new ConfigException( ERR_COMMON_AUDIT_UNSUPPORTED_LOG_ROTATION_POLICY.get(publisher.getDn(), policyConfig.dn())); } } fileRotation.setRotationFileSuffix(".yyyyMMddHHmmss"); auditConfig.setFileRotation(fileRotation); } private void addHandlerRetentionConfig(PublisherConfig publisher, HandlerConfigData config, FileBasedEventHandlerConfiguration auditConfig) throws ConfigException { SortedSet retentionPolicies = config.getRetentionPolicies(); if (retentionPolicies.isEmpty()) { return; } FileRetention fileRetention = new FileRetention(); for (final String policy : retentionPolicies) { LogRetentionPolicyCfg policyConfig = serverContext.getRootConfig().getLogRetentionPolicy(policy); if (policyConfig instanceof FileCountLogRetentionPolicyCfg) { fileRetention.setMaxNumberOfHistoryFiles(((FileCountLogRetentionPolicyCfg) policyConfig).getNumberOfFiles()); } else if (policyConfig instanceof FreeDiskSpaceLogRetentionPolicyCfg) { fileRetention.setMinFreeSpaceRequired(((FreeDiskSpaceLogRetentionPolicyCfg) policyConfig).getFreeDiskSpace()); } else if (policyConfig instanceof SizeLimitLogRetentionPolicyCfg) { fileRetention.setMaxDiskSpaceToUse(((SizeLimitLogRetentionPolicyCfg) policyConfig).getDiskSpaceUsed()); } else { throw new ConfigException( ERR_COMMON_AUDIT_UNSUPPORTED_LOG_RETENTION_POLICY.get(publisher.getDn(), policyConfig.dn())); } } auditConfig.setFileRetention(fileRetention); } /** * Convert the set of provided times of day using 24-hour format "HHmm" to a list of * times of day using duration in minutes, e.g "20 minutes". *

* Example: "0230" => "150 minutes" */ private List convertTimesOfDay(PublisherConfig publisher, FixedTimeLogRotationPolicyCfg policyConfig) throws ConfigException { SortedSet timesOfDay = policyConfig.getTimeOfDay(); List times = new ArrayList<>(); for (String timeOfDay : timesOfDay) { try { int time = Integer.valueOf(timeOfDay.substring(0, 2)) * 60 + Integer.valueOf(timeOfDay.substring(2, 4)); times.add(time + " minutes"); } catch (NumberFormatException | IndexOutOfBoundsException e) { throw new ConfigException(ERR_COMMON_AUDIT_INVALID_TIME_OF_DAY.get( publisher.getDn(), timeOfDay, stackTraceToSingleLineString(e))); } } return times; } private String getSecurePassword(PublisherConfig publisher, CsvConfigData config) { String fileName = config.getKeystorePinFile(); File pinFile = getFileForPath(fileName); if (!pinFile.exists()) { logger.warn(ERR_COMMON_AUDIT_KEYSTORE_PIN_FILE_MISSING.get(publisher.getDn(), pinFile)); return ""; } try (BufferedReader br = new BufferedReader(new FileReader(pinFile))) { String pinStr = br.readLine(); if (pinStr == null) { logger.warn(ERR_COMMON_AUDIT_KEYSTORE_PIN_FILE_CONTAINS_EMPTY_PIN.get(publisher.getDn(), pinFile)); return ""; } return pinStr; } catch (IOException ioe) { logger.warn(ERR_COMMON_AUDIT_ERROR_READING_KEYSTORE_PIN_FILE.get(publisher.getDn(), pinFile, stackTraceToSingleLineString(ioe)), ioe); return ""; } } /** * Indicates if the provided log publisher configuration corresponds to a common audit publisher. *

* The common audit publisher may not already exist. *

* This method must not be used when the corresponding configuration is deleted, because it * implies checking the corresponding configuration entry in the server. * * @param config * The log publisher configuration. * @return {@code true} if publisher corresponds to a common audit publisher * @throws ConfigException * If an error occurs */ public boolean isCommonAuditConfig(LogPublisherCfg config) throws ConfigException { return new PublisherConfig(serverContext, config).isCommonAudit; } /** * Indicates if the provided log publisher configuration corresponds to a common audit publisher. * * @param config * The log publisher configuration. * @return {@code true} if publisher is defined for common audit, {@code false} otherwise * @throws ConfigException * If an error occurs */ public boolean isExistingCommonAuditConfig(LogPublisherCfg config) throws ConfigException { String name = getConfigNormalizedName(config); return accessPublishers.containsKey(name) || httpAccessPublishers.containsKey(name); } /** * Indicates if HTTP access logging is enabled for common audit. * * @return {@code true} if there is at least one HTTP access logger enabled for common audit. */ public boolean isHttpAccessLogEnabled() { return !httpAccessPublishers.isEmpty(); } private String getConfigNormalizedName(LogPublisherCfg config) { return config.dn().toNormalizedUrlSafeString(); } /** * Returns the audit service that manages HTTP Access logging. * * @return the request handler that accepts audit events */ public RequestHandler getAuditServiceForHttpAccessLog() { return httpAccessAuditService; } /** * This class hides all ugly code needed to determine which type of publisher and audit event handler is needed. *

* In particular, it allows to retrieve a common configuration that can be used for log publishers that * publish to the same kind of handler. * For example: for CSV handler, DJ configurations for the log publishers contain the same methods but * do not have a common interface (CsvFileAccessLogPublisherCfg vs CsvFileHTTPAccessLogPublisherCfg). */ private static class PublisherConfig { private final LogPublisherCfg config; private final boolean isCommonAudit; private LogType logType = LogType.UNCONFIGURED; private AuditType auditType; PublisherConfig(ServerContext serverContext, LogPublisherCfg config) throws ConfigException { this.config = config; if (config instanceof JsonFileAccessLogPublisherCfg) { auditType = AuditType.JSON; logType = LogType.ACCESSLOG; } else if (config instanceof JsonFileHTTPAccessLogPublisherCfg) { auditType = AuditType.JSON; logType = LogType.HTTPLOG; } if (config instanceof CsvFileAccessLogPublisherCfg) { auditType = AuditType.CSV; logType = LogType.ACCESSLOG; } else if (config instanceof CsvFileHTTPAccessLogPublisherCfg) { auditType = AuditType.CSV; logType = LogType.HTTPLOG; } if (config instanceof ExternalAccessLogPublisherCfg) { auditType = AuditType.EXTERNAL; logType = LogType.ACCESSLOG; } else if (config instanceof ExternalHTTPAccessLogPublisherCfg) { auditType = AuditType.EXTERNAL; logType = LogType.HTTPLOG; } isCommonAudit = auditType != null; } DN getDn() { return config.dn(); } String getName() { return config.name(); } String getCommonAuditTopic() throws ConfigException { return logType.getCommonAuditTopic(config); } CsvConfigData getCsvConfig() throws ConfigException { return logType.getCsvConfig(config); } JsonConfigData getJsonConfig() throws ConfigException { return logType.getJsonConfig(config); } ExternalConfigData getExternalConfig() throws ConfigException { return logType.getExternalConfig(config); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof PublisherConfig)) { return false; } PublisherConfig other = (PublisherConfig) obj; return config.dn().equals(other.config.dn()); } @Override public int hashCode() { return config.dn().hashCode(); } } /** Types of audit handlers managed. */ private enum AuditType { CSV, JSON, EXTERNAL } /** Log types for LDAP or HTTP, to get specific configuration depending on the handler. */ private enum LogType { UNCONFIGURED { @Override String getCommonAuditTopic(LogPublisherCfg config) throws ConfigException { throw new ConfigException(ERR_COMMON_AUDIT_UNSUPPORTED_LOG_PUBLISHER.get(config.dn())); } @Override ExternalConfigData getExternalConfig(LogPublisherCfg config) throws ConfigException { throw new ConfigException(ERR_COMMON_AUDIT_UNSUPPORTED_LOG_PUBLISHER.get(config.dn())); } @Override CsvConfigData getCsvConfig(LogPublisherCfg config) throws ConfigException { throw new ConfigException(ERR_COMMON_AUDIT_UNSUPPORTED_LOG_PUBLISHER.get(config.dn())); } @Override JsonConfigData getJsonConfig(LogPublisherCfg config) throws ConfigException { throw new ConfigException(ERR_COMMON_AUDIT_UNSUPPORTED_LOG_PUBLISHER.get(config.dn())); } @Override boolean isHttp() { return false; } }, HTTPLOG { @Override public String getCommonAuditTopic(LogPublisherCfg config) { return "http-access"; } @Override public ExternalConfigData getExternalConfig(LogPublisherCfg config) throws ConfigException { ExternalHTTPAccessLogPublisherCfg conf = (ExternalHTTPAccessLogPublisherCfg) config; return new ExternalConfigData(conf.getConfigFile()); } @Override public CsvConfigData getCsvConfig(LogPublisherCfg config) throws ConfigException { CsvFileHTTPAccessLogPublisherCfg conf = (CsvFileHTTPAccessLogPublisherCfg) config; return new CsvConfigData(conf.getLogDirectory(), conf.getCsvQuoteChar(), conf.getCsvDelimiterChar(), conf .getCsvEolSymbols(), conf.isAsynchronous(), conf.isAutoFlush(), conf.isTamperEvident(), conf .getSignatureTimeInterval(), conf.getKeyStoreFile(), conf.getKeyStorePinFile(), conf.getRotationPolicy(), conf.getRetentionPolicy()); } @Override public JsonConfigData getJsonConfig(LogPublisherCfg config) throws ConfigException { JsonFileHTTPAccessLogPublisherCfg conf = (JsonFileHTTPAccessLogPublisherCfg) config; return new JsonConfigData(conf.getLogDirectory(), conf.getRotationPolicy(), conf.getRetentionPolicy()); } @Override public boolean isHttp() { return true; } }, ACCESSLOG { @Override public String getCommonAuditTopic(LogPublisherCfg config) { return "ldap-access"; } @Override public ExternalConfigData getExternalConfig(LogPublisherCfg config) throws ConfigException { ExternalAccessLogPublisherCfg conf = (ExternalAccessLogPublisherCfg) config; return new ExternalConfigData(conf.getConfigFile()); } @Override public CsvConfigData getCsvConfig(LogPublisherCfg config) throws ConfigException { CsvFileAccessLogPublisherCfg conf = (CsvFileAccessLogPublisherCfg) config; return new CsvConfigData(conf.getLogDirectory(), conf.getCsvQuoteChar(), conf.getCsvDelimiterChar(), conf .getCsvEolSymbols(), conf.isAsynchronous(), conf.isAutoFlush(), conf.isTamperEvident(), conf .getSignatureTimeInterval(), conf.getKeyStoreFile(), conf.getKeyStorePinFile(), conf.getRotationPolicy(), conf.getRetentionPolicy()); } @Override public JsonConfigData getJsonConfig(LogPublisherCfg config) throws ConfigException { JsonFileAccessLogPublisherCfg conf = (JsonFileAccessLogPublisherCfg) config; return new JsonConfigData(conf.getLogDirectory(), conf.getRotationPolicy(), conf.getRetentionPolicy()); } @Override public boolean isHttp() { return false; } }; abstract String getCommonAuditTopic(LogPublisherCfg config) throws ConfigException; abstract ExternalConfigData getExternalConfig(LogPublisherCfg config) throws ConfigException; abstract CsvConfigData getCsvConfig(LogPublisherCfg config) throws ConfigException; abstract JsonConfigData getJsonConfig(LogPublisherCfg config) throws ConfigException; abstract boolean isHttp(); } /** * Contains common parameters for non External CAUD handlers. *

* OpenDJ log publishers that log to a CAUD handler have the same parameters but do not share * a common ancestor with all the parameters (e.g Access Log, HTTP Access Log, ...), hence this class * is necessary to avoid duplicating code that setup configuration of the handler. */ private static abstract class HandlerConfigData { private final String logDirectory; private final SortedSet rotationPolicies; private final SortedSet retentionPolicies; HandlerConfigData(String logDirectory, SortedSet rotationPolicies, SortedSet retentionPolicies) { this.logDirectory = logDirectory; this.rotationPolicies = rotationPolicies; this.retentionPolicies = retentionPolicies; } String getLogDirectory() { return logDirectory; } SortedSet getRotationPolicies() { return rotationPolicies; } SortedSet getRetentionPolicies() { return retentionPolicies; } } /** Contains common parameters for the Json handler. */ private static class JsonConfigData extends HandlerConfigData { JsonConfigData(String logDirectory, SortedSet rotationPolicies, SortedSet retentionPolicies) { super(logDirectory, rotationPolicies, retentionPolicies); } } /** * Contains common parameters for the CSV handler. *

* Adds CSV specific configuration to a CAUD handler. */ private static class CsvConfigData extends HandlerConfigData { private final String eolSymbols; private final String delimiterChar; private final String quoteChar; private final boolean asynchronous; private final boolean autoFlush; private final boolean tamperEvident; private final long signatureTimeInterval; private final String keystoreFile; private final String keystorePinFile; CsvConfigData(String logDirectory, String quoteChar, String delimiterChar, String eolSymbols, boolean asynchronous, boolean autoFlush, boolean tamperEvident, long signatureTimeInterval, String keystoreFile, String keystorePinFile, SortedSet rotationPolicies, SortedSet retentionPolicies) { super(logDirectory, rotationPolicies, retentionPolicies); this.quoteChar = quoteChar; this.delimiterChar = delimiterChar; this.eolSymbols = eolSymbols; this.asynchronous = asynchronous; this.autoFlush = autoFlush; this.tamperEvident = tamperEvident; this.signatureTimeInterval = signatureTimeInterval; this.keystoreFile = keystoreFile; this.keystorePinFile = keystorePinFile; } String getEndOfLineSymbols() { return eolSymbols; } char getDelimiterChar() throws ConfigException { String filtered = delimiterChar.replaceAll(Pattern.quote("\\"), ""); if (filtered.length() != 1) { throw new ConfigException(ERR_COMMON_AUDIT_CSV_HANDLER_DELIMITER_CHAR.get("", filtered)); } return filtered.charAt(0); } public char getQuoteChar() throws ConfigException { String filtered = quoteChar.replaceAll(Pattern.quote("\\"), ""); if (filtered.length() != 1) { throw new ConfigException(ERR_COMMON_AUDIT_CSV_HANDLER_QUOTE_CHAR.get("", filtered)); } return filtered.charAt(0); } boolean isAsynchronous() { return asynchronous; } boolean isAutoFlush() { return autoFlush; } boolean isTamperEvident() { return tamperEvident; } long getSignatureTimeInterval() { return signatureTimeInterval; } String getKeystoreFile() { return keystoreFile; } String getKeystorePinFile() { return keystorePinFile; } } /** * Contains the parameters for an external handler. *

* OpenDJ log publishers that logs to an external handler have the same * parameters but do not share a common ancestor with all the parameters (e.g * Access Log, HTTP Access Log, ...), hence this class is necessary to avoid * duplicating code that setup the configuration of an external handler. */ private static class ExternalConfigData { private final String configurationFile; ExternalConfigData(String configurationFile) { this.configurationFile = configurationFile; } String getConfigurationFile() { return configurationFile; } } }