package org.owasp.esapi.reference;
import java.io.Serializable;
import java.util.HashMap;
import java.util.logging.Level;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.owasp.esapi.ESAPI;
import org.owasp.esapi.LogFactory;
import org.owasp.esapi.Logger;
import org.owasp.esapi.User;
/*
* Modified from JavaLogFactory for Logback --Gary Harris 7/4/2012
*/
/**
* Reference implementation of the LogFactory and Logger interfaces. This
* implementation uses the Java logging package, and marks each log message with
* the currently logged in user and the word "SECURITY" for security related
* events. See the <a
* href="JavaLogFactory.JavaLogger.html">JavaLogFactory.JavaLogger</a> Javadocs
* for the details on the JavaLogger reference implementation.
*
* @author Mike Fauzy (mike.fauzy@aspectsecurity.com) <a
* href="http://www.aspectsecurity.com">Aspect Security</a>
* @author Jeff Williams (jeff.williams .at. aspectsecurity.com) <a
* href="http://www.aspectsecurity.com">Aspect Security</a>
* @since June 1, 2007
* @see org.owasp.esapi.LogFactory
* @see org.owasp.esapi.reference.JavaLogFactory.JavaLogger
*/
public class LogbackLogFactory implements LogFactory {
private static volatile LogFactory singletonInstance;
private org.slf4j.Logger jlogger;
public static LogFactory getInstance() {
if (singletonInstance == null) {
synchronized (LogbackLogFactory.class) {
if (singletonInstance == null) {
singletonInstance = new LogbackLogFactory();
}
}
}
return singletonInstance;
}
private HashMap<Serializable, Logger> loggersMap = new HashMap<Serializable, Logger>();
/**
* Null argument constructor for this implementation of the LogFactory
* interface needed for dynamic configuration.
*/
public LogbackLogFactory() {
}
/**
* {@inheritDoc}
*/
public Logger getLogger(Class clazz) {
// If a logger for this class already exists, we return the same one, otherwise we create a new one.
Logger classLogger = (Logger) loggersMap.get(clazz);
if (classLogger == null) {
classLogger = new LogbackLogFactory.JavaLogger(clazz.getName());
loggersMap.put(clazz, classLogger);
}
return classLogger;
}
/**
* {@inheritDoc}
*/
public Logger getLogger(String moduleName) {
// If a logger for this module already exists, we return the same one, otherwise we create a new one.
Logger moduleLogger = (Logger) loggersMap.get(moduleName);
if (moduleLogger == null) {
moduleLogger = new LogbackLogFactory.JavaLogger(moduleName);
loggersMap.put(moduleName, moduleLogger);
}
return moduleLogger;
}
/**
* A custom logging level defined between Level.SEVERE and Level.WARNING in
* logger.
*/
public static class JavaLoggerLevel extends Level {
protected static final long serialVersionUID = 1L;
/**
* Defines a custom error level below SEVERE but above WARNING since
* this level isn't defined directly by java.util.Logger already.
*/
public static final Level ERROR_LEVEL = new JavaLogFactory.JavaLoggerLevel("ERROR", Level.SEVERE.intValue() - 1);
/**
* Constructs an instance of a JavaLoggerLevel which essentially
* provides a mapping between the name of the defined level and its
* numeric value.
*
* @param name The name of the JavaLoggerLevel
* @param value The associated numeric value
*/
protected JavaLoggerLevel(String name, int value) {
super(name, value);
}
}
/**
* Reference implementation of the Logger interface.
*
* It implements most of the recommendations defined in the Logger interface
* description. It does not filter out any sensitive data specific to the
* current application or organization, such as credit cards, social
* security numbers, etc.
*
* @author Jeff Williams (jeff.williams .at. aspectsecurity.com) <a
* href="http://www.aspectsecurity.com">Aspect Security</a>
* @since June 1, 2007
* @see org.owasp.esapi.LogFactory
*/
private static class JavaLogger implements org.owasp.esapi.Logger {
/**
* The jlogger object used by this class to log everything.
*/
//private java.util.logging.Logger jlogger = null;
private org.slf4j.Logger jlogger = null;
/**
* The module name using this log.
*/
private String moduleName = null;
/**
* The application name defined in ESAPI.properties
*/
private String applicationName = ESAPI.securityConfiguration().getApplicationName();
/**
* Log the application name?
*/
private static boolean logAppName = ESAPI.securityConfiguration().getLogApplicationName();
/**
* Log the server ip?
*/
private static boolean logServerIP = ESAPI.securityConfiguration().getLogServerIP();
/**
* Public constructor should only ever be called via the appropriate
* LogFactory
*
* @param moduleName the module name
*/
private JavaLogger(String moduleName) {
this.moduleName = moduleName;
//this.jlogger = java.util.logging.Logger.getLogger(applicationName + ":" + moduleName);
this.jlogger = org.slf4j.LoggerFactory.getLogger(applicationName + ":" + moduleName);
}
/**
* {@inheritDoc} Note: In this implementation, this change is not
* persistent, meaning that if the application is restarted, the log
* level will revert to the level defined in the ESAPI
* SecurityConfiguration properties file.
*/
public void setLevel(int level) {
try {
//jlogger.setLevel(convertESAPILeveltoLoggerLevel(level));
} catch (IllegalArgumentException e) {
this.error(Logger.SECURITY_FAILURE, "", e);
}
}
/**
* {@inheritDoc}
*
* @see org.owasp.esapi.reference.Log4JLogger#getESAPILevel()
*/
public int getESAPILevel() {
//return jlogger.getLevel().intValue();
return 0;
}
/**
* Converts the ESAPI logging level (a number) into the levels used by
* Java's logger.
*
* @param level The ESAPI to convert.
* @return The Java logging Level that is equivalent.
* @throws IllegalArgumentException if the supplied ESAPI level doesn't
* make a level that is currently defined.
*/
private static Level convertESAPILeveltoLoggerLevel(int level) {
switch (level) {
case Logger.OFF:
return Level.OFF;
case Logger.FATAL:
return Level.SEVERE;
case Logger.ERROR:
return JavaLogFactory.JavaLoggerLevel.ERROR_LEVEL; // This is a custom level.
case Logger.WARNING:
return Level.WARNING;
case Logger.INFO:
return Level.INFO;
case Logger.DEBUG:
return Level.FINE;
case Logger.TRACE:
return Level.FINEST;
case Logger.ALL:
return Level.ALL;
default: {
throw new IllegalArgumentException("Invalid logging level. Value was: " + level);
}
}
}
/**
* {@inheritDoc}
*/
public void trace(Logger.EventType type, String message, Throwable throwable) {
log(Level.FINEST, type, message, throwable);
}
/**
* {@inheritDoc}
*/
public void trace(Logger.EventType type, String message) {
log(Level.FINEST, type, message, null);
}
/**
* {@inheritDoc}
*/
public void debug(Logger.EventType type, String message, Throwable throwable) {
log(Level.FINE, type, message, throwable);
}
/**
* {@inheritDoc}
*/
public void debug(Logger.EventType type, String message) {
log(Level.FINE, type, message, null);
}
/**
* {@inheritDoc}
*/
public void info(Logger.EventType type, String message) {
log(Level.INFO, type, message, null);
}
/**
* {@inheritDoc}
*/
public void info(Logger.EventType type, String message, Throwable throwable) {
log(Level.INFO, type, message, throwable);
}
/**
* {@inheritDoc}
*/
public void warning(Logger.EventType type, String message, Throwable throwable) {
log(Level.WARNING, type, message, throwable);
}
/**
* {@inheritDoc}
*/
public void warning(Logger.EventType type, String message) {
log(Level.WARNING, type, message, null);
}
/**
* {@inheritDoc}
*/
public void error(Logger.EventType type, String message, Throwable throwable) {
log(Level.SEVERE, type, message, throwable);
}
/**
* {@inheritDoc}
*/
public void error(Logger.EventType type, String message) {
log(Level.SEVERE, type, message, null);
}
/**
* {@inheritDoc}
*/
public void fatal(Logger.EventType type, String message, Throwable throwable) {
log(Level.SEVERE, type, message, throwable);
}
/**
* {@inheritDoc}
*/
public void fatal(Logger.EventType type, String message) {
log(Level.SEVERE, type, message, null);
}
/**
* Log the message after optionally encoding any special characters that
* might be dangerous when viewed by an HTML based log viewer. Also
* encode any carriage returns and line feeds to prevent log injection
* attacks. This logs all the supplied parameters plus the user ID,
* user's source IP, a logging specific session ID, and the current
* date/time.
*
* It will only log the message if the current logging level is enabled,
* otherwise it will discard the message.
*
* @param level defines the set of recognized logging levels (TRACE,
* INFO, DEBUG, WARNING, ERROR, FATAL)
* @param type the type of the event (SECURITY SUCCESS, SECURITY
* FAILURE, EVENT SUCCESS, EVENT FAILURE)
* @param message the message
* @param throwable the throwable
*/
private void log(Level level, Logger.EventType type, String message, Throwable throwable) {
// Check to see if we need to log
//if (!jlogger.isLoggable(level)) {
// return;
//}
// ensure there's something to log
if (message == null) {
message = "";
}
// ensure no CRLF injection into logs for forging records
String clean = message.replace('\n', '_').replace('\r', '_');
if (ESAPI.securityConfiguration().getLogEncodingRequired()) {
clean = ESAPI.encoder().encodeForHTML(message);
if (!message.equals(clean)) {
clean += " (Encoded)";
}
}
// log server, port, app name, module name -- server:80/app/module
StringBuilder appInfo = new StringBuilder();
if (ESAPI.currentRequest() != null && logServerIP) {
appInfo.append(ESAPI.currentRequest().getLocalAddr() + ":" + ESAPI.currentRequest().getLocalPort());
}
if (logAppName) {
appInfo.append("/" + applicationName);
}
appInfo.append("/" + moduleName);
//get the type text if it exists
String typeInfo = "";
if (type != null) {
typeInfo += type + " ";
}
// log the message
jlogger.info("[" + typeInfo + getUserInfo() + " -> " + appInfo + "] " + clean, throwable);
}
/**
* {@inheritDoc}
*/
public boolean isDebugEnabled() {
return jlogger.isDebugEnabled();
}
/**
* {@inheritDoc}
*/
public boolean isErrorEnabled() {
return jlogger.isErrorEnabled();
}
/**
* {@inheritDoc}
*/
public boolean isFatalEnabled() {
return jlogger.isErrorEnabled();
}
/**
* {@inheritDoc}
*/
public boolean isInfoEnabled() {
return jlogger.isInfoEnabled();
}
/**
* {@inheritDoc}
*/
public boolean isTraceEnabled() {
return jlogger.isTraceEnabled();
}
/**
* {@inheritDoc}
*/
public boolean isWarningEnabled() {
return jlogger.isWarnEnabled();
}
public String getUserInfo() {
// create a random session number for the user to represent the user's 'session', if it doesn't exist already
String sid = null;
HttpServletRequest request = ESAPI.httpUtilities().getCurrentRequest();
if (request != null) {
HttpSession session = request.getSession(false);
if (session != null) {
sid = (String) session.getAttribute("ESAPI_SESSION");
// if there is no session ID for the user yet, we create one and store it in the user's session
if (sid == null) {
sid = "" + ESAPI.randomizer().getRandomInteger(0, 1000000);
session.setAttribute("ESAPI_SESSION", sid);
}
}
}
// log user information - username:session@ipaddr
User user = ESAPI.authenticator().getCurrentUser();
String userInfo = "";
//TODO - Make Type Logging configurable
if (user != null) {
userInfo += user.getAccountName() + ":" + sid + "@" + user.getLastHostAddress();
}
return userInfo;
}
/**
* {@inheritDoc}
*/
public void always(Logger.EventType type, String message) {
always(type, message, null);
}
/**
* {@inheritDoc}
*/
public void always(Logger.EventType type, String message, Throwable throwable) {
log(Level.OFF, type, message, throwable); // Seems backward, but this is what works, not Level.ALL
}
}
}
|