/**
* Copyright (c) Codice Foundation
* <p/>
* This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or any later version.
* <p/>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details. A copy of the GNU Lesser General Public License
* is distributed along with this program and can be found at
* <http://www.gnu.org/licenses/lgpl.html>.
*/
package ddf.security.common.audit;
import java.security.AccessController;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageUtils;
import org.apache.cxf.phase.PhaseInterceptorChain;
import org.apache.cxf.transport.http.AbstractHTTPDestination;
import org.apache.karaf.jaas.boot.principal.UserPrincipal;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.util.Supplier;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import ddf.security.SecurityConstants;
import ddf.security.SubjectUtils;
/**
* Class that contains utility methods for logging common security messages.
*/
public final class SecurityLogger {
private static final Logger LOGGER = LogManager.getLogger(SecurityConstants.SECURITY_LOGGER);
private static final String NO_USER = "UNKNOWN";
private static final boolean REQUIRE_AUDIT_ENCODING = Boolean.valueOf(System.getProperty(
"org.codice.ddf.platform.requireAuditEncoding",
"false"));
private SecurityLogger() {
}
private static String getUser(Subject subject) {
try {
if (subject == null) {
subject = ThreadContext.getSubject();
}
if (subject == null) {
javax.security.auth.Subject javaSubject = javax.security.auth.Subject.getSubject(
AccessController.getContext());
if (javaSubject != null) {
Set<UserPrincipal> userPrincipal = javaSubject.getPrincipals(UserPrincipal.class);
if (userPrincipal != null && userPrincipal.size() > 0) {
return userPrincipal.toArray(new UserPrincipal[1])[0].getName();
}
}
} else {
return SubjectUtils.getName(subject, NO_USER);
}
} catch (Exception e) {
//ignore and return NO_USER
}
return NO_USER;
}
private static void requestIpAndPortAndUserMessage(Message message,
StringBuilder messageBuilder) {
requestIpAndPortAndUserMessage(null, message, messageBuilder);
}
private static void requestIpAndPortAndUserMessage(Subject subject, Message message,
StringBuilder messageBuilder) {
String user = getUser(subject);
if (message != null) {
HttpServletRequest servletRequest = (HttpServletRequest) message.get(
AbstractHTTPDestination.HTTP_REQUEST);
// pull out the ip and port of the incoming connection so we know
// who is trying to get access
if (servletRequest != null) {
messageBuilder.append("Subject: ")
.append(user)
.append(" Request IP: ")
.append(servletRequest.getRemoteAddr())
.append(", Port: ")
.append(servletRequest.getRemotePort())
.append(" ");
} else if (MessageUtils.isOutbound(message)) {
messageBuilder.append("Subject: ")
.append(user)
.append(" Outbound endpoint: ")
.append(message.get(Message.ENDPOINT_ADDRESS))
.append(" ");
}
} else {
messageBuilder.append("Subject: ")
.append(user)
.append(" ");
}
}
/**
* Ensure that logs cannot be forged.
* @param message
* @return clean message
*/
private static String cleanAndEncode(String message) {
String clean = message.replace('\n', '_')
.replace('\r', '_');
if (REQUIRE_AUDIT_ENCODING) {
clean = StringEscapeUtils.escapeHtml(clean);
}
return clean;
}
/**
* Logs a message object with the {@link org.apache.logging.log4j.Level#INFO INFO} level.
*
* @param message the message string to log.
* @param subject the user subject to log
*/
public static void audit(String message, Subject subject) {
StringBuilder messageBuilder = new StringBuilder();
requestIpAndPortAndUserMessage(subject,
PhaseInterceptorChain.getCurrentMessage(),
messageBuilder);
LOGGER.info(messageBuilder.append(cleanAndEncode(message))
.toString());
}
/**
* Logs a message object with the {@link org.apache.logging.log4j.Level#INFO INFO} level.
*
* @param message the message string to log.
*/
public static void audit(String message) {
StringBuilder messageBuilder = new StringBuilder();
requestIpAndPortAndUserMessage(PhaseInterceptorChain.getCurrentMessage(), messageBuilder);
LOGGER.info(messageBuilder.append(cleanAndEncode(message))
.toString());
}
/**
* Logs a message with parameters at the {@link org.apache.logging.log4j.Level#INFO INFO} level.
*
* @param message the message to log; the format depends on the message factory.
* @param subject the user subject to log
* @param params parameters to the message.
*/
public static void audit(String message, Subject subject, Object... params) {
StringBuilder messageBuilder = new StringBuilder();
requestIpAndPortAndUserMessage(subject,
PhaseInterceptorChain.getCurrentMessage(),
messageBuilder);
LOGGER.info(messageBuilder.append(cleanAndEncode(message))
.toString(), params);
}
/**
* Logs a message with parameters at the {@link org.apache.logging.log4j.Level#INFO INFO} level.
*
* @param message the message to log; the format depends on the message factory.
* @param params parameters to the message.
*/
public static void audit(String message, Object... params) {
StringBuilder messageBuilder = new StringBuilder();
requestIpAndPortAndUserMessage(PhaseInterceptorChain.getCurrentMessage(), messageBuilder);
LOGGER.info(messageBuilder.append(cleanAndEncode(message))
.toString(), params);
}
/**
* Logs a message with parameters which are only to be constructed if the logging level is the {@link org.apache.logging.log4j.Level#INFO
* INFO} level.
*
* @param message the message to log; the format depends on the message factory.
* @param subject the user subject to log
* @param paramSuppliers An array of functions, which when called, produce the desired log message parameters.
*/
public static void audit(String message, Subject subject, Supplier... paramSuppliers) {
StringBuilder messageBuilder = new StringBuilder();
requestIpAndPortAndUserMessage(subject,
PhaseInterceptorChain.getCurrentMessage(),
messageBuilder);
LOGGER.info(messageBuilder.append(cleanAndEncode(message))
.toString(), paramSuppliers);
}
/**
* Logs a message with parameters which are only to be constructed if the logging level is the {@link org.apache.logging.log4j.Level#INFO
* INFO} level.
*
* @param message the message to log; the format depends on the message factory.
* @param paramSuppliers An array of functions, which when called, produce the desired log message parameters.
*/
public static void audit(String message, Supplier... paramSuppliers) {
StringBuilder messageBuilder = new StringBuilder();
requestIpAndPortAndUserMessage(PhaseInterceptorChain.getCurrentMessage(), messageBuilder);
LOGGER.info(messageBuilder.append(cleanAndEncode(message))
.toString(), paramSuppliers);
}
/**
* Logs a message at the {@link org.apache.logging.log4j.Level#INFO INFO} level including the stack trace of the {@link Throwable}
* <code>t</code> passed as parameter.
*
* @param message the message object to log.
* @param subject the user subject to log
* @param t the exception to log, including its stack trace.
*/
public static void audit(String message, Subject subject, Throwable t) {
StringBuilder messageBuilder = new StringBuilder();
requestIpAndPortAndUserMessage(subject,
PhaseInterceptorChain.getCurrentMessage(),
messageBuilder);
LOGGER.info(messageBuilder.append(cleanAndEncode(message))
.toString(), t);
}
/**
* Logs a message at the {@link org.apache.logging.log4j.Level#INFO INFO} level including the stack trace of the {@link Throwable}
* <code>t</code> passed as parameter.
*
* @param message the message object to log.
* @param t the exception to log, including its stack trace.
*/
public static void audit(String message, Throwable t) {
StringBuilder messageBuilder = new StringBuilder();
requestIpAndPortAndUserMessage(PhaseInterceptorChain.getCurrentMessage(), messageBuilder);
LOGGER.info(messageBuilder.append(cleanAndEncode(message))
.toString(), t);
}
/**
* Logs a message object with the {@link org.apache.logging.log4j.Level#WARN WARN} level.
*
* @param message the message string to log.
* @param subject the user subject to log
*/
public static void auditWarn(String message, Subject subject) {
StringBuilder messageBuilder = new StringBuilder();
requestIpAndPortAndUserMessage(subject,
PhaseInterceptorChain.getCurrentMessage(),
messageBuilder);
LOGGER.warn(messageBuilder.append(cleanAndEncode(message))
.toString());
}
/**
* Logs a message object with the {@link org.apache.logging.log4j.Level#WARN WARN} level.
*
* @param message the message string to log.
*/
public static void auditWarn(String message) {
StringBuilder messageBuilder = new StringBuilder();
requestIpAndPortAndUserMessage(PhaseInterceptorChain.getCurrentMessage(), messageBuilder);
LOGGER.warn(messageBuilder.append(cleanAndEncode(message))
.toString());
}
/**
* Logs a message with parameters at the {@link org.apache.logging.log4j.Level#WARN WARN} level.
*
* @param message the message to log; the format depends on the message factory.
* @param subject the user subject to log
* @param params parameters to the message.
*/
public static void auditWarn(String message, Subject subject, Object... params) {
StringBuilder messageBuilder = new StringBuilder();
requestIpAndPortAndUserMessage(subject,
PhaseInterceptorChain.getCurrentMessage(),
messageBuilder);
LOGGER.warn(messageBuilder.append(cleanAndEncode(message))
.toString(), params);
}
/**
* Logs a message with parameters at the {@link org.apache.logging.log4j.Level#WARN WARN} level.
*
* @param message the message to log; the format depends on the message factory.
* @param params parameters to the message.
*/
public static void auditWarn(String message, Object... params) {
StringBuilder messageBuilder = new StringBuilder();
requestIpAndPortAndUserMessage(PhaseInterceptorChain.getCurrentMessage(), messageBuilder);
LOGGER.warn(messageBuilder.append(cleanAndEncode(message))
.toString(), params);
}
/**
* Logs a message with parameters which are only to be constructed if the logging level is the {@link org.apache.logging.log4j.Level#WARN
* WARN} level.
*
* @param message the message to log; the format depends on the message factory.
* @param subject the user subject to log
* @param paramSuppliers An array of functions, which when called, produce the desired log message parameters.
*/
public static void auditWarn(String message, Subject subject, Supplier... paramSuppliers) {
StringBuilder messageBuilder = new StringBuilder();
requestIpAndPortAndUserMessage(subject,
PhaseInterceptorChain.getCurrentMessage(),
messageBuilder);
LOGGER.warn(messageBuilder.append(cleanAndEncode(message))
.toString(), paramSuppliers);
}
/**
* Logs a message with parameters which are only to be constructed if the logging level is the {@link org.apache.logging.log4j.Level#WARN
* WARN} level.
*
* @param message the message to log; the format depends on the message factory.
* @param paramSuppliers An array of functions, which when called, produce the desired log message parameters.
*/
public static void auditWarn(String message, Supplier... paramSuppliers) {
StringBuilder messageBuilder = new StringBuilder();
requestIpAndPortAndUserMessage(PhaseInterceptorChain.getCurrentMessage(), messageBuilder);
LOGGER.warn(messageBuilder.append(cleanAndEncode(message))
.toString(), paramSuppliers);
}
/**
* Logs a message at the {@link org.apache.logging.log4j.Level#WARN WARN} level including the stack trace of the {@link Throwable}
* <code>t</code> passed as parameter.
*
* @param message the message object to log.
* @param subject the user subject to log
* @param t the exception to log, including its stack trace.
*/
public static void auditWarn(String message, Subject subject, Throwable t) {
StringBuilder messageBuilder = new StringBuilder();
requestIpAndPortAndUserMessage(subject,
PhaseInterceptorChain.getCurrentMessage(),
messageBuilder);
LOGGER.warn(messageBuilder.append(cleanAndEncode(message))
.toString(), t);
}
/**
* Logs a message at the {@link org.apache.logging.log4j.Level#WARN WARN} level including the stack trace of the {@link Throwable}
* <code>t</code> passed as parameter.
*
* @param message the message object to log.
* @param t the exception to log, including its stack trace.
*/
public static void auditWarn(String message, Throwable t) {
StringBuilder messageBuilder = new StringBuilder();
requestIpAndPortAndUserMessage(PhaseInterceptorChain.getCurrentMessage(), messageBuilder);
LOGGER.warn(messageBuilder.append(cleanAndEncode(message))
.toString(), t);
}
}