opends/resource/config/config.ldif
@@ -727,6 +727,21 @@ ds-cfg-rotation-policy: cn=Size Limit Rotation Policy,cn=Log Rotation Policies,cn=config ds-cfg-retention-policy: cn=File Count Retention Policy,cn=Log Retention Policies,cn=config dn: cn=File-Based HTTP Access Logger,cn=Loggers,cn=config objectClass: top objectClass: ds-cfg-log-publisher objectClass: ds-cfg-http-access-log-publisher objectClass: ds-cfg-file-based-http-access-log-publisher cn: File-Based HTTP Access Logger ds-cfg-java-class: org.opends.server.loggers.TextHTTPAccessLogPublisher ds-cfg-enabled: true ds-cfg-log-file: logs/http-access ds-cfg-log-file-permissions: 640 ds-cfg-asynchronous: true ds-cfg-rotation-policy: cn=24 Hours Time Limit Rotation Policy,cn=Log Rotation Policies,cn=config ds-cfg-rotation-policy: cn=Size Limit Rotation Policy,cn=Log Rotation Policies,cn=config ds-cfg-retention-policy: cn=File Count Retention Policy,cn=Log Retention Policies,cn=config dn: cn=File-Based Audit Logger,cn=Loggers,cn=config objectClass: top objectClass: ds-cfg-log-publisher opends/resource/schema/02-config.ldif
@@ -3937,6 +3937,11 @@ ds-cfg-suppress-internal-operations $ ds-cfg-suppress-synchronization-operations ) X-ORIGIN 'OpenDS Directory Server' ) objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.16 NAME 'ds-cfg-http-access-log-publisher' SUP ds-cfg-log-publisher STRUCTURAL X-ORIGIN 'OpenDJ Directory Server' ) objectClasses: ( 1.3.6.1.4.1.26027.1.2.23 NAME 'ds-cfg-error-log-publisher' SUP ds-cfg-log-publisher @@ -3974,6 +3979,23 @@ ds-cfg-log-record-time-format $ ds-cfg-log-control-oids ) X-ORIGIN 'OpenDS Directory Server' ) objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.17 NAME 'ds-cfg-file-based-http-access-log-publisher' SUP ds-cfg-http-access-log-publisher STRUCTURAL MUST ( ds-cfg-log-file $ ds-cfg-asynchronous $ ds-cfg-log-file-permissions ) MAY ( ds-cfg-rotation-policy $ ds-cfg-rotation-action $ ds-cfg-retention-policy $ ds-cfg-time-interval $ ds-cfg-buffer-size $ ds-cfg-auto-flush $ ds-cfg-append $ ds-cfg-queue-size $ ds-cfg-log-record-time-format ) X-ORIGIN 'OpenDJ Directory Server' ) objectClasses: ( 1.3.6.1.4.1.26027.1.2.26 NAME 'ds-cfg-file-based-debug-log-publisher' SUP ds-cfg-debug-log-publisher opends/src/admin/defn/org/opends/server/admin/std/FileBasedHTTPAccessLogPublisherConfiguration.xml
New file @@ -0,0 +1,316 @@ <?xml version="1.0" encoding="utf-8"?> <!-- ! 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 ! trunk/opends/resource/legal-notices/OpenDS.LICENSE ! or https://OpenDS.dev.java.net/OpenDS.LICENSE. ! 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 ! trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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 2013 ForgeRock AS ! --> <adm:managed-object name="file-based-http-access-log-publisher" plural-name="file-based-http-access-log-publishers" package="org.opends.server.admin.std" extends="http-access-log-publisher" xmlns:adm="http://www.opends.org/admin" xmlns:ldap="http://www.opends.org/admin-ldap"> <adm:synopsis> <adm:user-friendly-plural-name /> publish HTTP access messages to the file system. </adm:synopsis> <adm:profile name="ldap"> <ldap:object-class> <ldap:name>ds-cfg-file-based-http-access-log-publisher</ldap:name> <ldap:superior>ds-cfg-http-access-log-publisher</ldap:superior> </ldap:object-class> </adm:profile> <adm:property-override name="java-class" advanced="true"> <adm:default-behavior> <adm:defined> <adm:value> org.opends.server.loggers.TextHTTPAccessLogPublisher </adm:value> </adm:defined> </adm:default-behavior> </adm:property-override> <adm:property name="asynchronous" mandatory="true" advanced="true"> <adm:synopsis> Indicates whether the <adm:user-friendly-name /> will publish records asynchronously. </adm:synopsis> <adm:default-behavior> <adm:defined> <adm:value>true</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:boolean /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-asynchronous</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="queue-size" advanced="true"> <adm:synopsis> The maximum number of log records that can be stored in the asynchronous queue. </adm:synopsis> <adm:description> Setting the queue size to zero activates parallel log writer implementation which has no queue size limit and as such the parallel log writer should only be used on a very well tuned server configuration to avoid potential out of memory errors. </adm:description> <adm:requires-admin-action> <adm:other> <adm:synopsis> The <adm:user-friendly-name /> must be restarted if this property is changed and the asynchronous property is set to true. </adm:synopsis> </adm:other> </adm:requires-admin-action> <adm:default-behavior> <adm:defined> <adm:value>5000</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:integer lower-limit="0" /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-queue-size</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="log-file" mandatory="true"> <adm:synopsis> The file name to use for the log files generated by the <adm:user-friendly-name />. The path to the file is relative to the server root. </adm:synopsis> <adm:requires-admin-action> <adm:component-restart /> </adm:requires-admin-action> <adm:syntax> <adm:string> <adm:pattern> <adm:regex>.*</adm:regex> <adm:usage>FILE</adm:usage> <adm:synopsis> A path to an existing file that is readable by the server. </adm:synopsis> </adm:pattern> </adm:string> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-log-file</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="log-file-permissions" mandatory="true"> <adm:synopsis> The UNIX permissions of the log files created by this <adm:user-friendly-name />. </adm:synopsis> <adm:default-behavior> <adm:defined> <adm:value>640</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:string> <adm:pattern> <adm:regex>^([0-7][0-7][0-7])$</adm:regex> <adm:usage>MODE</adm:usage> <adm:synopsis> A valid UNIX mode string. The mode string must contain three digits between zero and seven. </adm:synopsis> </adm:pattern> </adm:string> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-log-file-permissions</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="time-interval" advanced="true"> <adm:synopsis> Specifies the interval at which to check whether the log files need to be rotated. </adm:synopsis> <adm:default-behavior> <adm:defined> <adm:value>5s</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:duration base-unit="ms" lower-limit="1" /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-time-interval</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="buffer-size" advanced="true"> <adm:synopsis>Specifies the log file buffer size.</adm:synopsis> <adm:default-behavior> <adm:defined> <adm:value>64kb</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:size lower-limit="1" /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-buffer-size</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="auto-flush" advanced="true"> <adm:synopsis> Specifies whether to flush the writer after every log record. </adm:synopsis> <adm:description> If the asynchronous writes option is used, the writer is flushed after all the log records in the queue are written. </adm:description> <adm:default-behavior> <adm:defined> <adm:value>true</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:boolean /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-auto-flush</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="append"> <adm:synopsis> Specifies whether to append to existing log files. </adm:synopsis> <adm:default-behavior> <adm:defined> <adm:value>true</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:boolean /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-append</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="rotation-policy" multi-valued="true"> <adm:synopsis> The rotation policy to use for the <adm:user-friendly-name /> . </adm:synopsis> <adm:description> When multiple policies are used, rotation will occur if any policy's conditions are met. </adm:description> <adm:default-behavior> <adm:alias> <adm:synopsis> No rotation policy is used and log rotation will not occur. </adm:synopsis> </adm:alias> </adm:default-behavior> <adm:syntax> <adm:aggregation parent-path="/" relation-name="log-rotation-policy" /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-rotation-policy</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="retention-policy" multi-valued="true"> <adm:synopsis> The retention policy to use for the <adm:user-friendly-name /> . </adm:synopsis> <adm:description> When multiple policies are used, log files are cleaned when any of the policy's conditions are met. </adm:description> <adm:default-behavior> <adm:alias> <adm:synopsis> No retention policy is used and log files are never cleaned. </adm:synopsis> </adm:alias> </adm:default-behavior> <adm:syntax> <adm:aggregation parent-path="/" relation-name="log-retention-policy" /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-retention-policy</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="log-record-time-format"> <adm:synopsis> Specifies the format string that is used to generate log record timestamps. </adm:synopsis> <adm:default-behavior> <adm:defined> <adm:value>dd/MMM/yyyy:HH:mm:ss Z</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:string> <adm:pattern> <adm:regex>.*</adm:regex> <adm:usage>STRING</adm:usage> <adm:synopsis> Any valid format string that can be used with the java.text.SimpleDateFormat class. </adm:synopsis> </adm:pattern> </adm:string> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-log-record-time-format</ldap:name> </ldap:attribute> </adm:profile> </adm:property> </adm:managed-object> opends/src/admin/defn/org/opends/server/admin/std/HTTPAccessLogPublisherConfiguration.xml
New file @@ -0,0 +1,59 @@ <?xml version="1.0" encoding="utf-8"?> <!-- ! 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 ! trunk/opends/resource/legal-notices/OpenDS.LICENSE ! or https://OpenDS.dev.java.net/OpenDS.LICENSE. ! 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 ! trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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 2013 ForgeRock AS ! --> <adm:managed-object name="http-access-log-publisher" plural-name="http-access-log-publishers" package="org.opends.server.admin.std" extends="log-publisher" xmlns:adm="http://www.opends.org/admin" xmlns:ldap="http://www.opends.org/admin-ldap" xmlns:cli="http://www.opends.org/admin-cli"> <adm:synopsis> <adm:user-friendly-plural-name /> are responsible for distributing HTTP access log messages from the HTTP access logger to a destination. </adm:synopsis> <adm:description> HTTP access log messages provide information about the types of HTTP requests processed by the server. </adm:description> <adm:profile name="ldap"> <ldap:object-class> <ldap:name>ds-cfg-http-access-log-publisher</ldap:name> <ldap:superior>ds-cfg-log-publisher</ldap:superior> </ldap:object-class> </adm:profile> <adm:profile name="cli"> <cli:managed-object custom="true" /> </adm:profile> <adm:property-override name="java-class"> <adm:default-behavior> <adm:defined> <adm:value>org.opends.server.api.HTTPAccessLogPublisher</adm:value> </adm:defined> </adm:default-behavior> </adm:property-override> </adm:managed-object> opends/src/admin/messages/FileBasedHTTPAccessLogPublisherCfgDefn.properties
New file @@ -0,0 +1,26 @@ user-friendly-name=File Based HTTP Access Log Publisher user-friendly-plural-name=File Based HTTP Access Log Publishers synopsis=File Based HTTP Access Log Publishers publish HTTP access messages to the file system. property.append.synopsis=Specifies whether to append to existing log files. property.asynchronous.synopsis=Indicates whether the File Based HTTP Access Log Publisher will publish records asynchronously. property.auto-flush.synopsis=Specifies whether to flush the writer after every log record. property.auto-flush.description=If the asynchronous writes option is used, the writer is flushed after all the log records in the queue are written. property.buffer-size.synopsis=Specifies the log file buffer size. property.enabled.synopsis=Indicates whether the File Based HTTP Access Log Publisher is enabled for use. property.java-class.synopsis=The fully-qualified name of the Java class that provides the File Based HTTP Access Log Publisher implementation. property.log-file.synopsis=The file name to use for the log files generated by the File Based HTTP Access Log Publisher. The path to the file is relative to the server root. property.log-file.syntax.string.pattern.synopsis=A path to an existing file that is readable by the server. property.log-file-permissions.synopsis=The UNIX permissions of the log files created by this File Based HTTP Access Log Publisher. property.log-file-permissions.syntax.string.pattern.synopsis=A valid UNIX mode string. The mode string must contain three digits between zero and seven. property.log-record-time-format.synopsis=Specifies the format string that is used to generate log record timestamps. property.log-record-time-format.syntax.string.pattern.synopsis=Any valid format string that can be used with the java.text.SimpleDateFormat class. property.queue-size.synopsis=The maximum number of log records that can be stored in the asynchronous queue. property.queue-size.description=Setting the queue size to zero activates parallel log writer implementation which has no queue size limit and as such the parallel log writer should only be used on a very well tuned server configuration to avoid potential out of memory errors. property.queue-size.requires-admin-action.synopsis=The File Based HTTP Access Log Publisher must be restarted if this property is changed and the asynchronous property is set to true. property.retention-policy.synopsis=The retention policy to use for the File Based HTTP Access Log Publisher . property.retention-policy.description=When multiple policies are used, log files are cleaned when any of the policy's conditions are met. property.retention-policy.default-behavior.alias.synopsis=No retention policy is used and log files are never cleaned. property.rotation-policy.synopsis=The rotation policy to use for the File Based HTTP Access Log Publisher . property.rotation-policy.description=When multiple policies are used, rotation will occur if any policy's conditions are met. property.rotation-policy.default-behavior.alias.synopsis=No rotation policy is used and log rotation will not occur. property.time-interval.synopsis=Specifies the interval at which to check whether the log files need to be rotated. opends/src/admin/messages/HTTPAccessLogPublisherCfgDefn.properties
New file @@ -0,0 +1,6 @@ user-friendly-name=HTTP Access Log Publisher user-friendly-plural-name=HTTP Access Log Publishers synopsis=HTTP Access Log Publishers are responsible for distributing HTTP access log messages from the HTTP access logger to a destination. description=HTTP access log messages provide information about the types of HTTP requests processed by the server. property.enabled.synopsis=Indicates whether the HTTP Access Log Publisher is enabled for use. property.java-class.synopsis=The fully-qualified name of the Java class that provides the HTTP Access Log Publisher implementation. opends/src/messages/messages/config.properties
@@ -165,9 +165,6 @@ SEVERE_WARN_CONFIG_LOGGER_NO_ACTIVE_ERROR_LOGGERS_45=There are no active \ error loggers defined in the Directory Server configuration. No error \ logging will be performed MILD_WARN_CONFIG_LOGGER_NO_ACTIVE_DEBUG_LOGGERS_46=There are no active debug \ loggers defined in the Directory Server configuration. No debug logging will \ be performed SEVERE_ERR_CONFIG_LOGGER_ENTRY_UNACCEPTABLE_47=Configuration entry %s does \ not contain a valid logger configuration: %s. It will be ignored INFO_CONFIG_UNKNOWN_UNACCEPTABLE_REASON_48=Unknown unacceptable reason @@ -2178,3 +2175,9 @@ SEVERE_ERR_CONFIG_LOGGING_INVALID_TARGET_DN_PATTERN_730=Thee access log filtering \ criteria defined in "%s" could not be parsed because it contains an invalid \ target DN pattern "%s" SEVERE_WARN_CONFIG_LOGGER_NO_ACTIVE_HTTP_ACCESS_LOGGERS_731=There are no active \ HTTP access loggers defined in the Directory Server configuration. No HTTP \ access logging will be performed SEVERE_ERR_CONFIG_LOGGER_INVALID_HTTP_ACCESS_LOGGER_CLASS_732=Class %s specified \ in attribute ds-cfg-java-class of configuration entry %s cannot be \ instantiated as a Directory Server HTTP access logger: %s opends/src/server/org/opends/server/api/HTTPAccessLogPublisher.java
New file @@ -0,0 +1,101 @@ /* * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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 2013 ForgeRock AS. */ package org.opends.server.api; import java.util.List; import org.opends.messages.Message; import org.opends.server.admin.std.server.HTTPAccessLogPublisherCfg; import org.opends.server.loggers.HTTPRequestInfo; /** * This class defines the set of methods and structures that must be implemented * for a Directory Server HTTP access log publisher. * * @param <T> * The type of HTTP access log publisher configuration handled by this * log publisher implementation. */ @org.opends.server.types.PublicAPI( stability = org.opends.server.types.StabilityLevel.VOLATILE, mayInstantiate = false, mayExtend = true, mayInvoke = false) public abstract class HTTPAccessLogPublisher <T extends HTTPAccessLogPublisherCfg> implements LogPublisher<T> { /** {@inheritDoc} */ @Override public boolean isConfigurationAcceptable(T configuration, List<Message> unacceptableReasons) { // This default implementation does not perform any special // validation. It should be overridden by HTTP access log publisher // implementations that wish to perform more detailed validation. return true; } /** * Logs the request info according to the common logfile format. The common * logfile format is as follows: * * <pre> * remotehost rfc931 authuser [date] "request" status bytes "useragent" * </pre> * <dl> * <dt>remotehost</dt> * <dd>Remote hostname (or IP number if DNS hostname is not available, or if * DNSLookup is Off.</dd> * <dt>rfc931</dt> * <dd>The remote logname of the user.</dd> * <dt>authuser</dt> * <dd>The username as which the user has authenticated himself.</dd> * <dt>[date]</dt> * <dd>Date and time of the request.</dd> * <dt>"request"</dt> * <dd>The request line exactly as it came from the client.</dd> * <dt>status</dt> * <dd>The HTTP status code returned to the client.</dd> * <dt>bytes</dt> * <dd>The content-length of the document transferred.</dd> * <dt>"useragent"</dt> * <dd>The user agent that issued the request.</dd> * </dl> * <p> * <b>NOTE:</b> The bytes field is not currently supported. * </p> * * @param requestInfo * The request info to log */ public void logRequestInfo(HTTPRequestInfo requestInfo) { // Do nothing } } opends/src/server/org/opends/server/core/LoggerConfigManager.java
@@ -37,11 +37,17 @@ import org.opends.server.admin.server.ConfigurationAddListener; import org.opends.server.admin.server.ConfigurationDeleteListener; import org.opends.server.admin.server.ServerManagementContext; import org.opends.server.admin.std.server.*; import org.opends.server.admin.std.server.AccessLogPublisherCfg; import org.opends.server.admin.std.server.DebugLogPublisherCfg; import org.opends.server.admin.std.server.ErrorLogPublisherCfg; import org.opends.server.admin.std.server.HTTPAccessLogPublisherCfg; import org.opends.server.admin.std.server.LogPublisherCfg; import org.opends.server.admin.std.server.RootCfg; import org.opends.server.config.ConfigException; import org.opends.server.loggers.AbstractLogger; import org.opends.server.loggers.AccessLogger; import org.opends.server.loggers.ErrorLogger; import org.opends.server.loggers.HTTPAccessLogger; import org.opends.server.loggers.debug.DebugLogger; import org.opends.server.types.ConfigChangeResult; import org.opends.server.types.InitializationException; @@ -87,6 +93,9 @@ List<AccessLogPublisherCfg> accessPublisherCfgs = new ArrayList<AccessLogPublisherCfg>(); List<HTTPAccessLogPublisherCfg> httpAccessPublisherCfgs = new ArrayList<HTTPAccessLogPublisherCfg>(); List<ErrorLogPublisherCfg> errorPublisherCfgs = new ArrayList<ErrorLogPublisherCfg>(); @@ -102,6 +111,10 @@ { accessPublisherCfgs.add((AccessLogPublisherCfg)config); } else if (config instanceof HTTPAccessLogPublisherCfg) { httpAccessPublisherCfgs.add((HTTPAccessLogPublisherCfg) config); } else if(config instanceof ErrorLogPublisherCfg) { errorPublisherCfgs.add((ErrorLogPublisherCfg)config); @@ -116,10 +129,16 @@ // See if there are active loggers in all categories. If not, then log a // message. // Do not output warn message for debug loggers because it is valid to fully // disable all debug loggers. if (accessPublisherCfgs.isEmpty()) { logError(WARN_CONFIG_LOGGER_NO_ACTIVE_ACCESS_LOGGERS.get()); } if (httpAccessPublisherCfgs.isEmpty()) { logError(WARN_CONFIG_LOGGER_NO_ACTIVE_HTTP_ACCESS_LOGGERS.get()); } if (errorPublisherCfgs.isEmpty()) { logError(WARN_CONFIG_LOGGER_NO_ACTIVE_ERROR_LOGGERS.get()); @@ -127,6 +146,7 @@ DebugLogger.getInstance().initializeLogger(debugPublisherCfgs); AccessLogger.getInstance().initializeLogger(accessPublisherCfgs); HTTPAccessLogger.getInstance().initializeLogger(httpAccessPublisherCfgs); ErrorLogger.getInstance().initializeLogger(errorPublisherCfgs); } @@ -154,6 +174,10 @@ { return AccessLogger.getInstance(); } else if (config instanceof HTTPAccessLogPublisherCfg) { return HTTPAccessLogger.getInstance(); } else if (config instanceof ErrorLogPublisherCfg) { return ErrorLogger.getInstance(); opends/src/server/org/opends/server/loggers/HTTPAccessLogger.java
New file @@ -0,0 +1,148 @@ /* * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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 2013 ForgeRock AS */ package org.opends.server.loggers; import static org.opends.messages.ConfigMessages.*; import java.util.Collection; import org.opends.server.admin.ClassPropertyDefinition; import org.opends.server.admin.std.meta.HTTPAccessLogPublisherCfgDefn; import org.opends.server.admin.std.server.HTTPAccessLogPublisherCfg; import org.opends.server.api.HTTPAccessLogPublisher; /** * This class defines the wrapper that will invoke all registered HTTP access * loggers for each type of request received or response sent. */ public class HTTPAccessLogger extends AbstractLogger <HTTPAccessLogPublisher<HTTPAccessLogPublisherCfg>, HTTPAccessLogPublisherCfg> { private static LoggerStorage <HTTPAccessLogPublisher<HTTPAccessLogPublisherCfg>, HTTPAccessLogPublisherCfg> loggerStorage = new LoggerStorage <HTTPAccessLogPublisher<HTTPAccessLogPublisherCfg>, HTTPAccessLogPublisherCfg> (); /** The singleton instance of this class for configuration purposes. */ private static final HTTPAccessLogger instance = new HTTPAccessLogger(); /** * The constructor for this class. */ public HTTPAccessLogger() { super((Class) HTTPAccessLogPublisher.class, ERR_CONFIG_LOGGER_INVALID_HTTP_ACCESS_LOGGER_CLASS); } /** {@inheritDoc} */ @Override protected ClassPropertyDefinition getJavaClassPropertyDefinition() { return HTTPAccessLogPublisherCfgDefn.getInstance() .getJavaClassPropertyDefinition(); } /** {@inheritDoc} */ @Override protected LoggerStorage<HTTPAccessLogPublisher<HTTPAccessLogPublisherCfg>, HTTPAccessLogPublisherCfg> getStorage() { return loggerStorage; } /** * Retrieve the singleton instance of this class. * * @return The singleton instance of this logger. */ public static HTTPAccessLogger getInstance() { return instance; } /** * Add an HTTP access log publisher to the HTTP access logger. * * @param publisher * The HTTP access log publisher to add. */ public synchronized static void addHTTPAccessLogPublisher( HTTPAccessLogPublisher publisher) { loggerStorage.addLogPublisher(publisher); } /** * Remove an HTTP access log publisher from the HTTP access logger. * * @param publisher * The HTTP access log publisher to remove. * @return The publisher that was removed or null if it was not found. */ public synchronized static boolean removeHTTPAccessLogPublisher( HTTPAccessLogPublisher<HTTPAccessLogPublisherCfg> publisher) { return loggerStorage.removeLogPublisher(publisher); } /** * Removes all existing HTTP access log publishers from the logger. */ public synchronized static void removeAllHTTPAccessLogPublishers() { loggerStorage.removeAllLogPublishers(); } /** * Returns all the registered HTTP access log publishers. * * @return a Collection of {@link HTTPAccessLogPublisher} objects */ private static Collection<HTTPAccessLogPublisher<HTTPAccessLogPublisherCfg>> getHTTPAccessLogPublishers() { return loggerStorage.getLogPublishers(); } /** * Logs the given HTTPRequestInfo. * * @param requestInfo * the HTTP request info to log */ public static void logRequestInfo(HTTPRequestInfo requestInfo) { for (HTTPAccessLogPublisher<?> publisher : loggerStorage.getLogPublishers()) { publisher.logRequestInfo(requestInfo); } } } opends/src/server/org/opends/server/loggers/HTTPRequestInfo.java
New file @@ -0,0 +1,177 @@ /* * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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 2013 ForgeRock AS */ package org.opends.server.loggers; import javax.servlet.http.HttpServletRequest; /** * Contains the information required for logging the HTTP request. */ public class HTTPRequestInfo { /** The client's host. */ private final String remoteHost; /** The client's address. */ private final String remoteAddress; /** The protocol used for this request. */ private final String protocol; /** The HTTP method/verb used for this request. */ private final String method; /** The query issued by the client. */ private final String query; /** The user agent used by the client. */ private final String userAgent; /** The username that was used to authenticate. */ private String authUser; /** The HTTP status code returned to the client. */ private volatile Integer statusCode; /** * Constructor for this class. * * @param request * The {@link HttpServletRequest} for which to log the information */ public HTTPRequestInfo(HttpServletRequest request) { this.remoteHost = request.getRemoteHost(); this.remoteAddress = request.getRemoteAddr(); this.method = request.getMethod(); this.query = request.getRequestURI() + "/" + request.getQueryString(); this.protocol = request.getProtocol(); this.userAgent = request.getHeader("User-Agent"); } /** * Returns the client's host. * * @return the remoteHost */ public String getRemoteHost() { return remoteHost; } /** * Returns the client's address. * * @return the remoteAddress */ public String getRemoteAddress() { return remoteAddress; } /** * Returns the protocol used for this request. * * @return the protocol */ public String getProtocol() { return protocol; } /** * Returns the HTTP method/verb used for this request. * * @return the method */ public String getMethod() { return method; } /** * Returns the query issued by the client. * * @return the query */ public String getQuery() { return query; } /** * Returns the user agent used by the client. * * @return the userAgent */ public String getUserAgent() { return userAgent; } /** * Returns the username that was used to authenticate. * * @return the authUser */ public String getAuthUser() { return authUser; } /** * Sets the username that was used to authenticate. * * @param authUser * the authUser to set */ public void setAuthUser(String authUser) { this.authUser = authUser; } /** * Returns the HTTP status code returned to the client. * * @return the statusCode */ public int getStatusCode() { return statusCode != null ? statusCode : 200; } /** * Logs the current request info in the HTTP access log. * * @param statusCode * the HTTP status code that was returned to the client. */ public void log(int statusCode) { if (this.statusCode == null) { // this request was not logged before this.statusCode = statusCode; HTTPAccessLogger.logRequestInfo(this); } } } opends/src/server/org/opends/server/loggers/TextHTTPAccessLogPublisher.java
New file @@ -0,0 +1,409 @@ /* * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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 2013 ForgeRock AS */ package org.opends.server.loggers; import static org.opends.messages.ConfigMessages.*; import static org.opends.server.util.StaticUtils.*; import java.io.File; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; import org.opends.messages.Message; import org.opends.server.admin.server.ConfigurationChangeListener; import org.opends.server.admin.std.server.FileBasedHTTPAccessLogPublisherCfg; import org.opends.server.api.HTTPAccessLogPublisher; import org.opends.server.config.ConfigException; import org.opends.server.core.DirectoryServer; import org.opends.server.types.ConfigChangeResult; import org.opends.server.types.DN; import org.opends.server.types.DirectoryException; import org.opends.server.types.FilePermission; import org.opends.server.types.InitializationException; import org.opends.server.types.ResultCode; import org.opends.server.util.TimeThread; /** * This class provides the implementation of the HTTP access logger used by the * directory server. */ public final class TextHTTPAccessLogPublisher extends HTTPAccessLogPublisher<FileBasedHTTPAccessLogPublisherCfg> implements ConfigurationChangeListener<FileBasedHTTPAccessLogPublisherCfg> { /** * Returns an instance of the text HTTP access log publisher that will print * all messages to the provided writer. This is used to print the messages to * the console when the server starts up. * * @param writer * The text writer where the message will be written to. * @return The instance of the text error log publisher that will print all * messages to standard out. */ public static TextHTTPAccessLogPublisher getStartupTextHTTPAccessPublisher( final TextWriter writer) { final TextHTTPAccessLogPublisher startupPublisher = new TextHTTPAccessLogPublisher(); startupPublisher.writer = writer; return startupPublisher; } private TextWriter writer = null; private FileBasedHTTPAccessLogPublisherCfg cfg = null; private String timeStampFormat = "dd/MMM/yyyy:HH:mm:ss Z"; private DateFormat dateFormatter = new SimpleDateFormat(timeStampFormat); /** {@inheritDoc} */ @Override public ConfigChangeResult applyConfigurationChange( final FileBasedHTTPAccessLogPublisherCfg config) { // Default result code. ResultCode resultCode = ResultCode.SUCCESS; boolean adminActionRequired = false; final ArrayList<Message> messages = new ArrayList<Message>(); final File logFile = getFileForPath(config.getLogFile()); final FileNamingPolicy fnPolicy = new TimeStampNaming(logFile); try { final FilePermission perm = FilePermission.decodeUNIXMode(config .getLogFilePermissions()); final boolean writerAutoFlush = config.isAutoFlush() && !config.isAsynchronous(); TextWriter currentWriter; // Determine the writer we are using. If we were writing // asynchronously, we need to modify the underlying writer. if (writer instanceof AsyncronousTextWriter) { currentWriter = ((AsyncronousTextWriter) writer).getWrappedWriter(); } else if (writer instanceof ParallelTextWriter) { currentWriter = ((ParallelTextWriter) writer).getWrappedWriter(); } else { currentWriter = writer; } if (currentWriter instanceof MultifileTextWriter) { final MultifileTextWriter mfWriter = (MultifileTextWriter) currentWriter; mfWriter.setNamingPolicy(fnPolicy); mfWriter.setFilePermissions(perm); mfWriter.setAppend(config.isAppend()); mfWriter.setAutoFlush(writerAutoFlush); mfWriter.setBufferSize((int) config.getBufferSize()); mfWriter.setInterval(config.getTimeInterval()); mfWriter.removeAllRetentionPolicies(); mfWriter.removeAllRotationPolicies(); for (final DN dn : config.getRotationPolicyDNs()) { mfWriter.addRotationPolicy(DirectoryServer.getRotationPolicy(dn)); } for (final DN dn : config.getRetentionPolicyDNs()) { mfWriter.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn)); } if (writer instanceof AsyncronousTextWriter && !config.isAsynchronous()) { // The asynchronous setting is being turned off. final AsyncronousTextWriter asyncWriter = ((AsyncronousTextWriter) writer); writer = mfWriter; asyncWriter.shutdown(false); } if (writer instanceof ParallelTextWriter && !config.isAsynchronous()) { // The asynchronous setting is being turned off. final ParallelTextWriter asyncWriter = ((ParallelTextWriter) writer); writer = mfWriter; asyncWriter.shutdown(false); } if (!(writer instanceof AsyncronousTextWriter) && config.isAsynchronous()) { // The asynchronous setting is being turned on. final AsyncronousTextWriter asyncWriter = new AsyncronousTextWriter( "Asyncronous Text Writer for " + config.dn().toNormalizedString(), config.getQueueSize(), config.isAutoFlush(), mfWriter); writer = asyncWriter; } if (!(writer instanceof ParallelTextWriter) && config.isAsynchronous()) { // The asynchronous setting is being turned on. final ParallelTextWriter asyncWriter = new ParallelTextWriter( "Parallel Text Writer for " + config.dn().toNormalizedString(), config.isAutoFlush(), mfWriter); writer = asyncWriter; } if ((cfg.isAsynchronous() && config.isAsynchronous()) && (cfg.getQueueSize() != config.getQueueSize())) { adminActionRequired = true; } if (!config.getLogRecordTimeFormat().equals(timeStampFormat)) { TimeThread.removeUserDefinedFormatter(timeStampFormat); setTimeStampFormat(config.getLogRecordTimeFormat()); } cfg = config; } } catch (final Exception e) { final Message message = ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get( config.dn().toString(), stackTraceToSingleLineString(e)); resultCode = DirectoryServer.getServerErrorResultCode(); messages.add(message); } return new ConfigChangeResult(resultCode, adminActionRequired, messages); } /** {@inheritDoc} */ @Override public void initializeLogPublisher( final FileBasedHTTPAccessLogPublisherCfg cfg) throws ConfigException, InitializationException { final File logFile = getFileForPath(cfg.getLogFile()); final FileNamingPolicy fnPolicy = new TimeStampNaming(logFile); try { final FilePermission perm = FilePermission.decodeUNIXMode(cfg .getLogFilePermissions()); final LogPublisherErrorHandler errorHandler = new LogPublisherErrorHandler(cfg.dn()); final boolean writerAutoFlush = cfg.isAutoFlush() && !cfg.isAsynchronous(); final MultifileTextWriter theWriter = new MultifileTextWriter( "Multifile Text Writer for " + cfg.dn().toNormalizedString(), cfg.getTimeInterval(), fnPolicy, perm, errorHandler, "UTF-8", writerAutoFlush, cfg.isAppend(), (int) cfg.getBufferSize()); // Validate retention and rotation policies. for (final DN dn : cfg.getRotationPolicyDNs()) { theWriter.addRotationPolicy(DirectoryServer.getRotationPolicy(dn)); } for (final DN dn : cfg.getRetentionPolicyDNs()) { theWriter.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn)); } if (cfg.isAsynchronous()) { if (cfg.getQueueSize() > 0) { this.writer = new AsyncronousTextWriter( "Asyncronous Text Writer for " + cfg.dn().toNormalizedString(), cfg.getQueueSize(), cfg.isAutoFlush(), theWriter); } else { this.writer = new ParallelTextWriter("Parallel Text Writer for " + cfg.dn().toNormalizedString(), cfg.isAutoFlush(), theWriter); } } else { this.writer = theWriter; } } catch (final DirectoryException e) { final Message message = ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(cfg .dn().toString(), String.valueOf(e)); throw new InitializationException(message, e); } catch (final IOException e) { final Message message = ERR_CONFIG_LOGGING_CANNOT_OPEN_FILE.get( logFile.toString(), cfg.dn().toString(), String.valueOf(e)); throw new InitializationException(message, e); } this.cfg = cfg; setTimeStampFormat(cfg.getLogRecordTimeFormat()); cfg.addFileBasedHTTPAccessChangeListener(this); } /** {@inheritDoc} */ @Override public boolean isConfigurationAcceptable( final FileBasedHTTPAccessLogPublisherCfg configuration, final List<Message> unacceptableReasons) { return isConfigurationChangeAcceptable(configuration, unacceptableReasons); } /** {@inheritDoc} */ @Override public boolean isConfigurationChangeAcceptable( final FileBasedHTTPAccessLogPublisherCfg config, final List<Message> unacceptableReasons) { // Validate the time-stamp formatter. final String formatString = config.getLogRecordTimeFormat(); try { new SimpleDateFormat(formatString); } catch (final Exception e) { final Message message = ERR_CONFIG_LOGGING_INVALID_TIME_FORMAT.get(String .valueOf(formatString)); unacceptableReasons.add(message); return false; } // Make sure the permission is valid. try { final FilePermission filePerm = FilePermission.decodeUNIXMode(config .getLogFilePermissions()); if (!filePerm.isOwnerWritable()) { final Message message = ERR_CONFIG_LOGGING_INSANE_MODE.get(config .getLogFilePermissions()); unacceptableReasons.add(message); return false; } } catch (final DirectoryException e) { final Message message = ERR_CONFIG_LOGGING_MODE_INVALID.get( config.getLogFilePermissions(), String.valueOf(e)); unacceptableReasons.add(message); return false; } return true; } /** {@inheritDoc} */ @Override public final void close() { writer.shutdown(); TimeThread.removeUserDefinedFormatter(timeStampFormat); if (cfg != null) { cfg.removeFileBasedHTTPAccessChangeListener(this); } } private void setTimeStampFormat(String timeStampFormat) { this.timeStampFormat = timeStampFormat; this.dateFormatter = new SimpleDateFormat(timeStampFormat); } /** {@inheritDoc} */ @Override public final DN getDN() { return cfg != null ? cfg.dn() : null; } /** {@inheritDoc} */ @Override public void logRequestInfo(HTTPRequestInfo ri) { final StringBuilder sb = new StringBuilder(100); // remotehost if (ri.getRemoteHost() != null) { sb.append(ri.getRemoteHost()); } else { sb.append(ri.getRemoteAddress()); } // rfc931 - not supported // authuser sb.append(" "); if (ri.getAuthUser() != null) { sb.append(ri.getAuthUser()); } // [dateAndTime] sb.append(" [").append(TimeThread.getUserDefinedTime(timeStampFormat)) .append("]"); // "request" sb.append(" \"").append(ri.getMethod()); sb.append(" ").append(ri.getQuery()); sb.append(" ").append(ri.getProtocol()).append("\""); // HTTP response status code sb.append(" ").append(ri.getStatusCode()); // bytes - not supported // "user agent" sb.append(" \"").append(ri.getUserAgent()).append("\""); writer.writeRecord(sb.toString()); } } opends/src/server/org/opends/server/protocols/http/CollectClientConnectionsFilter.java
@@ -44,11 +44,11 @@ import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import org.forgerock.json.resource.ResourceException; import org.forgerock.opendj.ldap.Connection; @@ -66,6 +66,7 @@ import org.forgerock.opendj.rest2ldap.servlet.Rest2LDAPContextFactory; import org.opends.messages.Message; import org.opends.server.admin.std.server.ConnectionHandlerCfg; import org.opends.server.loggers.HTTPRequestInfo; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.schema.SchemaConstants; import org.opends.server.types.AddressMask; @@ -85,8 +86,8 @@ private final class HTTPRequestContext { private AsyncContext asyncContext; private ServletRequest request; private ServletResponse response; private HttpServletRequest request; private HttpServletResponse response; private FilterChain chain; private HTTPClientConnection clientConnection; @@ -94,8 +95,12 @@ /** Whether to pretty print the resulting JSON. */ private boolean prettyPrint; /** Can be used for the bind request. */ /** Used for the bind request when credentials are specified. */ private String userName; /** Used for the bind request when credentials are specified. */ private String password; /** Request information for logging. */ private HTTPRequestInfo requestInfo; } /** @@ -176,6 +181,8 @@ @Override public void handleResult(BindResult result) { ctx.requestInfo.setAuthUser(ctx.userName); final AuthenticationInfo authInfo = new AuthenticationInfo(to(resultEntry), to(resultEntry.getName()), ByteString.valueOf(ctx.password), false); @@ -231,23 +238,44 @@ /** {@inheritDoc} */ @Override public void doFilter(ServletRequest request, ServletResponse response, public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) { final boolean prettyPrint = final HttpServletRequest request = (HttpServletRequest) req; final HttpServletResponse response = (HttpServletResponse) resp; final HTTPRequestContext ctx = new HTTPRequestContext(); ctx.request = request; ctx.response = new HttpServletResponseWrapper(response) { /** {@inheritDoc} */ @Override public void setStatus(int sc) { ctx.requestInfo.log(sc); super.setStatus(sc); } /** {@inheritDoc} */ @Override public void setStatus(int sc, String sm) { ctx.requestInfo.log(sc); super.setStatus(sc, sm); } }; ctx.chain = chain; ctx.prettyPrint = Boolean.parseBoolean(request.getParameter("_prettyPrint")); ctx.requestInfo = new HTTPRequestInfo(ctx.request); final HTTPClientConnection clientConnection = new HTTPClientConnection(this.connectionHandler, request); this.connectionHandler.addClientConnection(clientConnection); final HTTPRequestContext ctx = new HTTPRequestContext(); ctx.request = request; ctx.response = response; ctx.chain = chain; ctx.clientConnection = clientConnection; ctx.prettyPrint = prettyPrint; try { @@ -259,17 +287,18 @@ // checked. logConnect(clientConnection); ctx.connection = new SdkConnectionAdapter(clientConnection); ctx.connection = new SdkConnectionAdapter(clientConnection, ctx.requestInfo); final String[] userPassword = extractUsernamePassword(request); if (userPassword != null && userPassword.length == 2) { final String userName = userPassword[0]; ctx.userName = userPassword[0]; ctx.password = userPassword[1]; ctx.asyncContext = getAsyncContext(request); ctx.connection.searchSingleEntryAsync(buildSearchRequest(userName), ctx.connection.searchSingleEntryAsync(buildSearchRequest(ctx.userName), new DoBindResultHandler(ctx)); } else if (this.connectionHandler.acceptUnauthenticatedRequests()) @@ -308,13 +337,13 @@ private void sendAuthenticationFailure(HTTPRequestContext ctx) { final int statusCode = HttpServletResponse.SC_UNAUTHORIZED; try { // The user could not be authenticated. Send an HTTP Basic authentication // challenge if HTTP Basic authentication is enabled. ResourceException unauthorizedException = ResourceException.getException(HttpServletResponse.SC_UNAUTHORIZED, "Invalid Credentials"); ResourceException.getException(statusCode, "Invalid Credentials"); sendErrorReponse(ctx.response, ctx.prettyPrint, unauthorizedException); ctx.clientConnection.disconnect(DisconnectReason.INVALID_CREDENTIALS, @@ -322,6 +351,8 @@ } finally { ctx.requestInfo.log(statusCode); if (ctx.asyncContext != null) { ctx.asyncContext.complete(); @@ -331,6 +362,7 @@ private void onFailure(Exception e, HTTPRequestContext ctx) { ResourceException ex = Rest2LDAP.asResourceException(e); try { if (debugEnabled()) @@ -338,8 +370,7 @@ TRACER.debugCaught(DebugLogLevel.ERROR, e); } sendErrorReponse(ctx.response, ctx.prettyPrint, Rest2LDAP .asResourceException(e)); sendErrorReponse(ctx.response, ctx.prettyPrint, ex); Message message = INFO_CONNHANDLER_UNABLE_TO_REGISTER_CLIENT.get(ctx.clientConnection @@ -352,6 +383,8 @@ } finally { ctx.requestInfo.log(ex.getCode()); if (ctx.asyncContext != null) { ctx.asyncContext.complete(); @@ -359,7 +392,7 @@ } } private boolean canProcessRequest(ServletRequest request, private boolean canProcessRequest(HttpServletRequest request, final HTTPClientConnection clientConnection) throws UnknownHostException { InetAddress clientAddr = InetAddress.getByName(request.getRemoteAddr()); @@ -415,20 +448,18 @@ * @throws ResourceException * if any error occur */ String[] extractUsernamePassword(ServletRequest request) String[] extractUsernamePassword(HttpServletRequest request) throws ResourceException { HttpServletRequest req = (HttpServletRequest) request; // TODO Use session to reduce hits with search + bind? // Use proxied authorization control for session. if (authConfig.isCustomHeadersAuthenticationSupported()) { final String userName = req.getHeader(authConfig.getCustomHeaderUsername()); request.getHeader(authConfig.getCustomHeaderUsername()); final String password = req.getHeader(authConfig.getCustomHeaderPassword()); request.getHeader(authConfig.getCustomHeaderPassword()); if (userName != null && password != null) { return new String[] { userName, password }; @@ -437,7 +468,7 @@ if (authConfig.isBasicAuthenticationSupported()) { String httpBasicAuthHeader = req.getHeader(HTTP_BASIC_AUTH_HEADER); String httpBasicAuthHeader = request.getHeader(HTTP_BASIC_AUTH_HEADER); if (httpBasicAuthHeader != null) { String[] userPassword = parseUsernamePassword(httpBasicAuthHeader); @@ -463,26 +494,23 @@ * @param re * the resource exception with the error response content */ void sendErrorReponse(ServletResponse response, boolean prettyPrint, void sendErrorReponse(HttpServletResponse response, boolean prettyPrint, ResourceException re) { HttpServletResponse resp = (HttpServletResponse) response; resp.setStatus(re.getCode()); response.setStatus(re.getCode()); if (re.getCode() == HttpServletResponse.SC_UNAUTHORIZED && authConfig.isBasicAuthenticationSupported()) { resp.setHeader("WWW-Authenticate", response.setHeader("WWW-Authenticate", "Basic realm=\"org.forgerock.opendj\""); } try { // Send error JSON document out resp.setHeader("Content-Type", "application/json"); ServletOutputStream out = resp.getOutputStream(); out.println(toJSON(prettyPrint, re)); response.setHeader("Content-Type", "application/json"); response.getOutputStream().println(toJSON(prettyPrint, re)); } catch (IOException ignore) { opends/src/server/org/opends/server/protocols/http/SdkConnectionAdapter.java
@@ -33,6 +33,8 @@ import java.util.LinkedHashSet; import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.http.HttpServletResponse; import org.forgerock.opendj.ldap.AbstractAsynchronousConnection; import org.forgerock.opendj.ldap.ConnectionEventListener; import org.forgerock.opendj.ldap.ErrorResultException; @@ -68,6 +70,7 @@ import org.opends.server.core.QueueingStrategy; import org.opends.server.core.SearchOperationBasis; import org.opends.server.core.UnbindOperationBasis; import org.opends.server.loggers.HTTPRequestInfo; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.types.AuthenticationInfo; import org.opends.server.types.ByteString; @@ -91,6 +94,9 @@ /** The HTTP client connection being "adapted". */ private final HTTPClientConnection clientConnection; /** The HTTP request information to log. */ private final HTTPRequestInfo requestInfo; /** * The next message ID (and operation ID) that should be used for this * connection. @@ -111,10 +117,14 @@ * * @param clientConnection * the HTTP client connection being "adapted" * @param requestInfo * the HTTP request information to log */ public SdkConnectionAdapter(HTTPClientConnection clientConnection) public SdkConnectionAdapter(HTTPClientConnection clientConnection, HTTPRequestInfo requestInfo) { this.clientConnection = clientConnection; this.requestInfo = requestInfo; this.queueingStrategy = new BoundedWorkQueueStrategy(clientConnection.getConnectionHandler() .getCurrentConfig().getMaxConcurrentOpsPerConnection()); @@ -223,6 +233,11 @@ { this.clientConnection.disconnect(DisconnectReason.UNBIND, false, null); } // At this point, we try to log the request with OK status code. // If it was already logged, it will be a no op. this.requestInfo.log(HttpServletResponse.SC_OK); isClosed = true; } opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java
@@ -51,7 +51,14 @@ import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; import java.util.*; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.logging.ConsoleHandler; import java.util.logging.Handler; import java.util.logging.LogManager; @@ -78,8 +85,10 @@ import org.opends.server.extensions.ConfigFileHandler; import org.opends.server.loggers.AccessLogger; import org.opends.server.loggers.ErrorLogger; import org.opends.server.loggers.HTTPAccessLogger; import org.opends.server.loggers.TextAccessLogPublisher; import org.opends.server.loggers.TextErrorLogPublisher; import org.opends.server.loggers.TextHTTPAccessLogPublisher; import org.opends.server.loggers.debug.DebugLogger; import org.opends.server.loggers.debug.TextDebugLogPublisher; import org.opends.server.plugins.InvocationCounterPlugin; @@ -187,23 +196,17 @@ public static final String PROPERTY_OPENDMK_LOCATION = "org.opends.server.snmp.opendmk"; /** * The test text writer for the Debug Logger */ public static TestTextWriter DEBUG_TEXT_WRITER = new TestTextWriter(); /** The test text writer for the Debug Logger */ public static TestTextWriter DEBUG_TEXT_WRITER = new TestTextWriter(); /** * The test text writer for the Debug Logger */ public static TestTextWriter ERROR_TEXT_WRITER = new TestTextWriter(); /** The test text writer for the Error Logger */ public static TestTextWriter ERROR_TEXT_WRITER = new TestTextWriter(); /** * The test text writer for the Debug Logger */ public static TestTextWriter ACCESS_TEXT_WRITER = new TestTextWriter(); /** The test text writer for the Access Logger */ public static TestTextWriter ACCESS_TEXT_WRITER = new TestTextWriter(); /** The test text writer for the HTTP Access Logger */ public static TestTextWriter HTTP_ACCESS_TEXT_WRITER = new TestTextWriter(); /** * Indicates whether the server has already been started. The value of this @@ -525,6 +528,9 @@ TextAccessLogPublisher.getStartupTextAccessPublisher( ACCESS_TEXT_WRITER, false)); HTTPAccessLogger.addHTTPAccessLogPublisher(TextHTTPAccessLogPublisher .getStartupTextHTTPAccessPublisher(HTTP_ACCESS_TEXT_WRITER)); // Use more verbose tool logger. ErrorLogger.addErrorLogPublisher( TextErrorLogPublisher.getToolStartupTextErrorPublisher(