Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update the logging properties to opt-out of the prefix events #844 #845

Merged
merged 10 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions configuration/esapi/ESAPI.properties
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,10 @@ Logger.UserInfo=true
# Determines whether ESAPI should log the session id and client IP.
Logger.ClientInfo=true

# Determines whether ESAPI should log the prefix of [EVENT_TYPE - APPLICATION NAME].
# If all above Logger entries are set to false, as well as LogPrefix, then the output would be the same as if no ESAPI was used
Logger.LogPrefix=true

#===========================================================================
# ESAPI Intrusion Detection
#
Expand Down
1 change: 1 addition & 0 deletions src/main/java/org/owasp/esapi/PropNames.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ public final class PropNames {
public static final String LOG_ENCODING_REQUIRED = "Logger.LogEncodingRequired";
public static final String LOG_APPLICATION_NAME = "Logger.LogApplicationName";
public static final String LOG_SERVER_IP = "Logger.LogServerIP";
public static final String LOG_PREFIX = "Logger.LogPrefix";

public static final String VALIDATION_PROPERTIES = "Validator.ConfigurationFile";
public static final String VALIDATION_PROPERTIES_MULTIVALUED = "Validator.ConfigurationFile.MultiValued";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ public interface EsapiPropertyLoader {
*/
public Boolean getBooleanProp(String propertyName) throws ConfigurationException;

/**
* Get any Boolean type property from security configuration.
* If property does not exist in configuration or has incorrect type, defaultValue is returned
Copy link
Contributor

@kwwall kwwall Aug 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the Javadoc: for this interface: "or has incorrect type".

Well, that is the $64k question, isn't it? What is the INTUITIVE behavior if one calls something like:

    Boolean propValue = getBooleanProp("SomePropName", true)

but somewhere, say in ESAPI.properties or elsewhere, we have defined

SomePropName=This is not a boolean property

Then what is the expected / intuitive behavior for that call? I think that I'd expect a ConfigurationException to be thrown rather than having propValue silently being set to true. Or maybe I shouldn't say that is the intuitive behavior, but rather the safe behavior. I think because copy/paste errors abound and it would be easy to make a typo with the property name, that's the only way that we're going to help people catch these bizarre accidental edge cases. Otherwise, we're going to cause them to curse at us because they'll have to figure up a debugger to figure out what the hell is going on in cases like this.

@xeno6696 and @jeremiahjstacey - I'd like your opinion here. I know this requires yet another code change, but if we do a release and document it this way this (likely bug, IMO) will become a feature because changing it later on would break backward compatibility. If we were requiring JDK 9 or later we could just not export this method and keep it hidden, but since Java 8 is the minimal version, once we add this, it becomes available to the world.

I personally am leaning towards only using the default value if the property is not set at all; that is if

    System.getProperty("SomePropValue");

returns null, but I'd like to know what the 2 of you think. If you both think it's more intuitive have it return the default boolean value if the property name is found to have a non-Boolean value I will reluctantly go along with that.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, I think this is more scope than we should put time into to modify the opt-out request from the original issue. I appreciate the initiative; however, we're adding this case to expand for something that could have been solved in two copies of an 6 line block.

This is overengineering in this scope. We're blocking value, and a new possible release for this API change.

My suggestion is to take it out. Use the duplicate snippet from my previous comment in the 2 factories and revert the changes in the DefaultSecurityConfiguration. Create another task for the default API that we can address (reuse this code if you want), and complete it in a non-blocking scope. We don't need this to fix the original problem, and as you can see the discussion of edge cases and caveats will continue to grow. Let's get the opt-out behavior in in a first form, then improve upon it in another task to expand the SecurityConfiguration API to allow defaults to be specified.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I concur. If in the future, you want to do a new PR to get a new interface for

public Boolean getBooleanProp(String propertyName, Boolean defaultValue) throws ConfigurationException

I am fine with that, but as my review of this as surfaced, this is a little more nuanced than intuition alone might have us believe. If we are going to add that a future time, I think we need to start out with a GitHub issue and discuss the specifications there first, before waiting for an implementation to discuss them. So, to proceed with this PR, please rip that part of the code out and follow Jeremiah's suggestion.

* @return property value.
*/
public Boolean getBooleanProp(String propertyName, Boolean defaultValue);

/**
* Get any property from security configuration. As every property can be returned as string, this method
* throws exception only when property does not exist.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,18 @@ public Boolean getBooleanProp(String propertyName) throws ConfigurationException
throw new ConfigurationException("Could not find property " + propertyName + " in configuration");
}

/**
* {@inheritDoc}
*/
@Override
public Boolean getBooleanProp(String propertyName, Boolean defaultValue) {
for (AbstractPrioritizedPropertyLoader loader : loaders) {
return loader.getBooleanProp(propertyName, defaultValue);
Copy link
Contributor

@kwwall kwwall Aug 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the way this is currently implemented, I don't think it will ever reach the final return on line 87 here. Maybe I'm wrong about that. Do we have a test for that case? Of course, if we change it as per my previous comment, we may still need it. Or may need to add a try / catch here and just re-throw any ConfigurationException.

}
return defaultValue;
}


/**
* {@inheritDoc}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,24 @@ public Boolean getBooleanProp(String propertyName) throws ConfigurationException
}
}

/**
* {@inheritDoc}
*/
@Override
public Boolean getBooleanProp(String propertyName, Boolean defaultValue) {
String property = properties.getProperty(propertyName);
if (property == null) {
return defaultValue;
}
if (property.equalsIgnoreCase("true") || property.equalsIgnoreCase("yes")) {
return true;
}
if (property.equalsIgnoreCase("false") || property.equalsIgnoreCase("no")) {
return false;
}
return defaultValue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depending on if we decide to change the behavior here in accordance to my first comment, we may wish to change line 88 to something along the following lines:

    if ( property != null ) {
        throw new ConfigurationException("Property name '" + propertyName + "' has non-Boolean value of '" + property + "'.");
    }

}

/**
* {@inheritDoc}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,24 @@ public Boolean getBooleanProp(String propertyName) throws ConfigurationException
}
}

/**
* {@inheritDoc}
*/
@Override
public Boolean getBooleanProp(String propertyName, Boolean defaultValue) {
String property = properties.getProperty(propertyName);
if (property == null) {
return defaultValue;
}
if (property.equalsIgnoreCase("true") || property.equalsIgnoreCase("yes")) {
return true;
}
if (property.equalsIgnoreCase("false") || property.equalsIgnoreCase("no")) {
return false;
}
return defaultValue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here. Similar to comment on StandardEsapiPropertyLoader.java.

}

/**
* {@inheritDoc}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,24 @@ public class EventTypeLogSupplier // implements Supplier<String>
{
/** EventType reference to supply log representation of. */
private final EventType eventType;
/** Whether to log or not the event type */
private boolean logEventType = true;

/**
* Ctr
*
* @param evtyp EventType reference to supply log representation for
* @param eventType EventType reference to supply log representation for
kwwall marked this conversation as resolved.
Show resolved Hide resolved
*/
public EventTypeLogSupplier(EventType evtyp) {
this.eventType = evtyp == null ? Logger.EVENT_UNSPECIFIED : evtyp;
public EventTypeLogSupplier(EventType eventType) {
this.eventType = eventType == null ? Logger.EVENT_UNSPECIFIED : eventType;
}

// @Override -- Uncomment when we switch to Java 8 as minimal baseline.
public String get() {
return eventType.toString();
return logEventType ? eventType.toString() : "";
}

public void setLogEventType(boolean logEventType) {
this.logEventType = logEventType;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,27 +35,47 @@ public class LogPrefixAppender implements LogAppender {
private final boolean logApplicationName;
/** Application Name to record. */
private final String appName;
/** Whether or not to print the prefix. */
private final boolean logPrefix;

/**
* Ctr.
* Constructor
*
* @param logUserInfo Whether or not to record user information
* @param logClientInfo Whether or not to record client information
* @param logServerIp Whether or not to record server ip information
* @param logApplicationName Whether or not to record application name
* @param appName Application Name to record.
* @param logPrefix is set by default to true
*/
@SuppressWarnings("JavadocReference")
public LogPrefixAppender(boolean logUserInfo, boolean logClientInfo, boolean logServerIp, boolean logApplicationName, String appName) {
this(logUserInfo, logClientInfo, logServerIp, logApplicationName, appName, true);
}

/**
* Constructor
*
* @param logUserInfo Whether or not to record user information
* @param logClientInfo Whether or not to record client information
* @param logServerIp Whether or not to record server ip information
* @param logApplicationName Whether or not to record application name
* @param appName Application Name to record.
* @param logPrefix Whether or not to print the prefix
*/
public LogPrefixAppender(boolean logUserInfo, boolean logClientInfo, boolean logServerIp, boolean logApplicationName, String appName, boolean logPrefix) {
this.logUserInfo = logUserInfo;
this.logClientInfo = logClientInfo;
this.logServerIp = logServerIp;
this.logApplicationName = logApplicationName;
this.appName = appName;
this.logPrefix = logPrefix;
}

@Override
public String appendTo(String logName, EventType eventType, String message) {
EventTypeLogSupplier eventTypeSupplier = new EventTypeLogSupplier(eventType);
eventTypeSupplier.setLogEventType(this.logPrefix);

UserInfoSupplier userInfoSupplier = new UserInfoSupplier();
userInfoSupplier.setLogUserInfo(logUserInfo);
Expand All @@ -66,6 +86,7 @@ public String appendTo(String logName, EventType eventType, String message) {
ServerInfoSupplier serverInfoSupplier = new ServerInfoSupplier(logName);
serverInfoSupplier.setLogServerIp(logServerIp);
serverInfoSupplier.setLogApplicationName(logApplicationName, appName);
serverInfoSupplier.setLogLogName(logPrefix);

String eventTypeMsg = eventTypeSupplier.get().trim();
String userInfoMsg = userInfoSupplier.get().trim();
Expand All @@ -80,17 +101,20 @@ public String appendTo(String logName, EventType eventType, String message) {

String[] optionalPrefixContent = new String[] {userInfoMsg + clientInfoMsg, serverInfoMsg};

StringBuilder logPrefix = new StringBuilder();
//EventType is always appended
logPrefix.append(eventTypeMsg);
StringBuilder logPrefixBuilder = new StringBuilder();
//EventType is always appended (unless we specifically asked not to Log Prefix)
if (this.logPrefix) {
logPrefixBuilder.append(eventTypeMsg);
}

for (String element : optionalPrefixContent) {
if (!element.isEmpty()) {
logPrefix.append(" ");
logPrefix.append(element);
logPrefixBuilder.append(" ");
logPrefixBuilder.append(element);
}
}

return String.format(RESULT_FORMAT, logPrefix.toString(), message);
String logPrefixContent = logPrefixBuilder.toString();
return logPrefixContent.trim().isEmpty() ? message : String.format(RESULT_FORMAT, logPrefixContent, message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ public class ServerInfoSupplier // implements Supplier<String>
private boolean logAppName = true;
/** The application name to log. */
private String applicationName = "";

/** Whether to log the Name */
private boolean logLogName = true;
/** Reference to the associated logname/module name. */
private final String logName;

Expand All @@ -57,10 +58,14 @@ public String get() {
appInfo.append(request.getLocalAddr()).append(":").append(request.getLocalPort());
}
}
if (logAppName) {
appInfo.append("/").append(applicationName);

if (this.logAppName) {
appInfo.append("/").append(this.applicationName);
}

if (this.logLogName) {
appInfo.append("/").append(logName);
}
appInfo.append("/").append(logName);

return appInfo.toString();
}
Expand All @@ -74,6 +79,15 @@ public void setLogServerIp(boolean log) {
this.logServerIP = log;
}

/**
* Specify whether the instance should record the prefix.
*
* @param logLogName {@code true} to record
*/
public void setLogLogName(boolean logLogName) {
this.logLogName = logLogName;
}

/**
* Specify whether the instance should record the application name
*
Expand Down
18 changes: 17 additions & 1 deletion src/main/java/org/owasp/esapi/logging/java/JavaLogFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static org.owasp.esapi.PropNames.LOG_ENCODING_REQUIRED;
import static org.owasp.esapi.PropNames.LOG_SERVER_IP;
import static org.owasp.esapi.PropNames.LOG_USER_INFO;
import static org.owasp.esapi.PropNames.LOG_PREFIX;

import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -79,7 +80,8 @@ public class JavaLogFactory implements LogFactory {
boolean logApplicationName = ESAPI.securityConfiguration().getBooleanProp(LOG_APPLICATION_NAME);
String appName = ESAPI.securityConfiguration().getStringProp(APPLICATION_NAME);
boolean logServerIp = ESAPI.securityConfiguration().getBooleanProp(LOG_SERVER_IP);
JAVA_LOG_APPENDER = createLogAppender(logUserInfo, logClientInfo, logServerIp, logApplicationName, appName);
boolean logPrefix = ESAPI.securityConfiguration().getBooleanProp(LOG_PREFIX, true);
JAVA_LOG_APPENDER = createLogAppender(logUserInfo, logClientInfo, logServerIp, logApplicationName, appName, logPrefix);

Map<Integer, JavaLogLevelHandler> levelLookup = new HashMap<>();
levelLookup.put(Logger.ALL, JavaLogLevelHandlers.ALWAYS);
Expand Down Expand Up @@ -144,6 +146,20 @@ public class JavaLogFactory implements LogFactory {
return new LogPrefixAppender(logUserInfo, logClientInfo, logServerIp, logApplicationName, appName);
}

/**
* Populates the default log appender for use in factory-created loggers.
* @param appName
* @param logApplicationName
* @param logServerIp
* @param logClientInfo
* @param logPrefix
*
* @return LogAppender instance.
*/
/*package*/ static LogAppender createLogAppender(boolean logUserInfo, boolean logClientInfo, boolean logServerIp, boolean logApplicationName, String appName, boolean logPrefix) {
return new LogPrefixAppender(logUserInfo, logClientInfo, logServerIp, logApplicationName, appName, logPrefix);
}


@Override
public Logger getLogger(String moduleName) {
Expand Down
17 changes: 16 additions & 1 deletion src/main/java/org/owasp/esapi/logging/slf4j/Slf4JLogFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import static org.owasp.esapi.PropNames.LOG_APPLICATION_NAME;
import static org.owasp.esapi.PropNames.APPLICATION_NAME;
import static org.owasp.esapi.PropNames.LOG_SERVER_IP;
import static org.owasp.esapi.PropNames.LOG_PREFIX;
import org.slf4j.LoggerFactory;
/**
* LogFactory implementation which creates SLF4J supporting Loggers.
Expand Down Expand Up @@ -69,7 +70,8 @@ public class Slf4JLogFactory implements LogFactory {
boolean logApplicationName = ESAPI.securityConfiguration().getBooleanProp(LOG_APPLICATION_NAME);
String appName = ESAPI.securityConfiguration().getStringProp(APPLICATION_NAME);
boolean logServerIp = ESAPI.securityConfiguration().getBooleanProp(LOG_SERVER_IP);
SLF4J_LOG_APPENDER = createLogAppender(logUserInfo, logClientInfo, logServerIp, logApplicationName, appName);
boolean logPrefix = ESAPI.securityConfiguration().getBooleanProp(LOG_PREFIX, true);
SLF4J_LOG_APPENDER = createLogAppender(logUserInfo, logClientInfo, logServerIp, logApplicationName, appName, logPrefix);

Map<Integer, Slf4JLogLevelHandler> levelLookup = new HashMap<>();
levelLookup.put(Logger.ALL, Slf4JLogLevelHandlers.TRACE);
Expand Down Expand Up @@ -114,6 +116,19 @@ public class Slf4JLogFactory implements LogFactory {
return new LogPrefixAppender(logUserInfo, logClientInfo, logServerIp, logApplicationName, appName);
}

/**
* Populates the default log appender for use in factory-created loggers.
* @param appName
* @param logApplicationName
* @param logServerIp
* @param logClientInfo
* @param logPrefix
*
* @return LogAppender instance.
*/
/*package*/ static LogAppender createLogAppender(boolean logUserInfo, boolean logClientInfo, boolean logServerIp, boolean logApplicationName, String appName, boolean logPrefix) {
return new LogPrefixAppender(logUserInfo, logClientInfo, logServerIp, logApplicationName, appName, logPrefix);
}

@Override
public Logger getLogger(String moduleName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1441,21 +1441,47 @@ public Boolean getBooleanProp(String propertyName) throws ConfigurationException
try {
return esapiPropertyManager.getBooleanProp(propertyName);
} catch (ConfigurationException ex) {
String property = properties.getProperty( propertyName );
String property = properties.getProperty(propertyName);
if ( property == null ) {
throw new ConfigurationException( "SecurityConfiguration for " + propertyName + " not found in ESAPI.properties");
}
if ( property.equalsIgnoreCase("true") || property.equalsIgnoreCase("yes" ) ) {
if ( property.equalsIgnoreCase("true") || property.equalsIgnoreCase("yes") ) {
return true;
}
if ( property.equalsIgnoreCase("false") || property.equalsIgnoreCase( "no" ) ) {
if ( property.equalsIgnoreCase("false") || property.equalsIgnoreCase("no") ) {
return false;
}
throw new ConfigurationException( "SecurityConfiguration for " + propertyName + " has incorrect " +
"type");
}
}

/**
* {@inheritDoc}
* Looks for property in three configuration files in following order:
* 1.) In file defined as org.owasp.esapi.opsteam system property
* 2.) In file defined as org.owasp.esapi.devteam system property
* 3.) In ESAPI.properties
*/
@Override
public Boolean getBooleanProp(String propertyName, Boolean defaultValue) {
try {
return esapiPropertyManager.getBooleanProp(propertyName);
} catch (ConfigurationException ex) {
String property = properties.getProperty(propertyName);
if ( property == null ) {
return defaultValue;
}
if ( property.equalsIgnoreCase("true") || property.equalsIgnoreCase("yes") ) {
return true;
}
if ( property.equalsIgnoreCase("false") || property.equalsIgnoreCase("no") ) {
return false;
}
return defaultValue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto comment in EsapiPropertyManager.java about throwing ConfigurationException here.

}
}

/**
* {@inheritDoc}
* Looks for property in three configuration files in following order:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,11 @@ public Boolean getBooleanProp(String propertyName) throws ConfigurationException
return wrapped.getBooleanProp(propertyName);
}

@Override
public Boolean getBooleanProp(String propertyName, Boolean defaultValue) {
return wrapped.getBooleanProp(propertyName, defaultValue);
}

@Override
public String getStringProp(String propertyName) throws ConfigurationException {
return wrapped.getStringProp(propertyName);
Expand Down
Loading