diff --git a/pom.xml b/pom.xml
index 3dcdaa12..9b8fd761 100644
--- a/pom.xml
+++ b/pom.xml
@@ -76,6 +76,8 @@
+ src/main/java
+ test/main/java
org.apache.maven.plugins
@@ -589,13 +591,33 @@
true
-
-
- net.ricecode
- string-similarity
- 1.0.0
+
+
+ junit
+ junit
+ test
+ 4.12
+
+
+ org.hamcrest
+ java-hamcrest
+ test
+ 2.0.0.0
+
+
+ org.mockito
+ mockito-core
+ test
+ 2.0.5-beta
+
+
+
+
+ net.ricecode
+ string-similarity
+ 1.0.0
compile
true
-
+
diff --git a/src/main/java/fr/xephi/authme/Log4JFilter.java b/src/main/java/fr/xephi/authme/Log4JFilter.java
index dccaee6f..365b2cec 100644
--- a/src/main/java/fr/xephi/authme/Log4JFilter.java
+++ b/src/main/java/fr/xephi/authme/Log4JFilter.java
@@ -6,80 +6,50 @@ import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.message.Message;
+import fr.xephi.authme.util.StringUtils;
+
/**
- *
+ * Implements a filter for Log4j to skip sensitive AuthMe commands.
* @author Xephi59
*/
public class Log4JFilter implements org.apache.logging.log4j.core.Filter {
+
+ /** List of commands (lower-case) to skip. */
+ private static final String[] COMMANDS_TO_SKIP = { "/login ", "/l ", "/reg ", "/changepassword ",
+ "/unregister ", "/authme register ", "/authme changepassword ", "/authme reg ", "/authme cp ",
+ "/register " };
+ /** Constructor. */
public Log4JFilter() {
}
@Override
public Result filter(LogEvent record) {
- try {
- if (record == null || record.getMessage() == null)
- return Result.NEUTRAL;
- String logM = record.getMessage().getFormattedMessage().toLowerCase();
- if (!logM.contains("issued server command:"))
- return Result.NEUTRAL;
- if (!logM.contains("/login ") && !logM.contains("/l ") && !logM.contains("/reg ") && !logM.contains("/changepassword ") && !logM.contains("/unregister ") && !logM.contains("/authme register ") && !logM.contains("/authme changepassword ") && !logM.contains("/authme reg ") && !logM.contains("/authme cp ") && !logM.contains("/register "))
- return Result.NEUTRAL;
- return Result.DENY;
- } catch (NullPointerException npe) {
- return Result.NEUTRAL;
- }
+ if (record == null) {
+ return Result.NEUTRAL;
+ }
+ return validateMessage(record.getMessage());
}
@Override
public Result filter(Logger arg0, Level arg1, Marker arg2, String message,
Object... arg4) {
- try {
- if (message == null)
- return Result.NEUTRAL;
- String logM = message.toLowerCase();
- if (!logM.contains("issued server command:"))
- return Result.NEUTRAL;
- if (!logM.contains("/login ") && !logM.contains("/l ") && !logM.contains("/reg ") && !logM.contains("/changepassword ") && !logM.contains("/unregister ") && !logM.contains("/authme register ") && !logM.contains("/authme changepassword ") && !logM.contains("/authme reg ") && !logM.contains("/authme cp ") && !logM.contains("/register "))
- return Result.NEUTRAL;
- return Result.DENY;
- } catch (NullPointerException npe) {
- return Result.NEUTRAL;
- }
+ return validateMessage(message);
}
@Override
public Result filter(Logger arg0, Level arg1, Marker arg2, Object message,
Throwable arg4) {
- try {
- if (message == null)
- return Result.NEUTRAL;
- String logM = message.toString().toLowerCase();
- if (!logM.contains("issued server command:"))
- return Result.NEUTRAL;
- if (!logM.contains("/login ") && !logM.contains("/l ") && !logM.contains("/reg ") && !logM.contains("/changepassword ") && !logM.contains("/unregister ") && !logM.contains("/authme register ") && !logM.contains("/authme changepassword ") && !logM.contains("/authme reg ") && !logM.contains("/authme cp ") && !logM.contains("/register "))
- return Result.NEUTRAL;
- return Result.DENY;
- } catch (NullPointerException npe) {
- return Result.NEUTRAL;
- }
+ if (message == null) {
+ return Result.NEUTRAL;
+ }
+ return validateMessage(message.toString());
}
@Override
public Result filter(Logger arg0, Level arg1, Marker arg2, Message message,
Throwable arg4) {
- try {
- if (message == null)
- return Result.NEUTRAL;
- String logM = message.getFormattedMessage().toLowerCase();
- if (!logM.contains("issued server command:"))
- return Result.NEUTRAL;
- if (!logM.contains("/login ") && !logM.contains("/l ") && !logM.contains("/reg ") && !logM.contains("/changepassword ") && !logM.contains("/unregister ") && !logM.contains("/authme register ") && !logM.contains("/authme changepassword ") && !logM.contains("/authme reg ") && !logM.contains("/authme cp ") && !logM.contains("/register "))
- return Result.NEUTRAL;
- return Result.DENY;
- } catch (NullPointerException npe) {
- return Result.NEUTRAL;
- }
+ return validateMessage(message);
}
@Override
@@ -92,4 +62,39 @@ public class Log4JFilter implements org.apache.logging.log4j.core.Filter {
return Result.NEUTRAL;
}
+ /**
+ * Validates a Message instance and returns the {@link Result} value
+ * depending depending on whether the message contains sensitive AuthMe
+ * data.
+ *
+ * @param message the Message object to verify
+ * @return the Result value
+ */
+ private static Result validateMessage(Message message) {
+ if (message == null) {
+ return Result.NEUTRAL;
+ }
+ return validateMessage(message.getFormattedMessage());
+ }
+
+ /**
+ * Validates a message and returns the {@link Result} value depending
+ * depending on whether the message contains sensitive AuthMe data.
+ *
+ * @param message the message to verify
+ * @return the Result value
+ */
+ private static Result validateMessage(String message) {
+ if (message == null) {
+ return Result.NEUTRAL;
+ }
+
+ String lowerMessage = message.toLowerCase();
+ if (lowerMessage.contains("issued server command:")
+ && StringUtils.containsAny(lowerMessage, COMMANDS_TO_SKIP)) {
+ return Result.DENY;
+ }
+ return Result.NEUTRAL;
+ }
+
}
diff --git a/src/main/java/fr/xephi/authme/util/StringUtils.java b/src/main/java/fr/xephi/authme/util/StringUtils.java
index eab7ea15..da6f91ba 100644
--- a/src/main/java/fr/xephi/authme/util/StringUtils.java
+++ b/src/main/java/fr/xephi/authme/util/StringUtils.java
@@ -25,4 +25,16 @@ public class StringUtils {
// Determine the difference value, return the result
return Math.abs(service.score(first, second) - 1.0);
}
+
+ public static boolean containsAny(String str, String... pieces) {
+ if (str == null) {
+ return false;
+ }
+ for (String piece : pieces) {
+ if (str.contains(piece)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/test/main/java/fr/xephi/authme/Log4JFilterTest.java b/test/main/java/fr/xephi/authme/Log4JFilterTest.java
new file mode 100644
index 00000000..5614eb51
--- /dev/null
+++ b/test/main/java/fr/xephi/authme/Log4JFilterTest.java
@@ -0,0 +1,230 @@
+package fr.xephi.authme;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.when;
+
+import org.apache.logging.log4j.core.Filter.Result;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.message.Message;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+/**
+ * Test for {@link Log4JFilter}.
+ */
+public class Log4JFilterTest {
+
+ private final Log4JFilter log4JFilter = new Log4JFilter();
+
+ private static final String SENSITIVE_COMMAND = "User issued server command: /login pass pass";
+ private static final String NORMAL_COMMAND = "User issued server command: /help";
+ private static final String OTHER_COMMAND = "Starting the server... Write /l for logs";
+
+ // ---------
+ // Test the filter(LogEvent) method
+ // ---------
+ @Test
+ public void shouldFilterSensitiveLogEvent() {
+ // given
+ Message message = mockMessage(SENSITIVE_COMMAND);
+ LogEvent event = Mockito.mock(LogEvent.class);
+ when(event.getMessage()).thenReturn(message);
+
+ // when
+ Result result = log4JFilter.filter(event);
+
+ // then
+ assertThat(result, equalTo(Result.DENY));
+ }
+
+ @Test
+ public void shouldNotFilterIrrelevantLogEvent() {
+ // given
+ Message message = mockMessage(NORMAL_COMMAND);
+ LogEvent event = Mockito.mock(LogEvent.class);
+ when(event.getMessage()).thenReturn(message);
+
+ // when
+ Result result = log4JFilter.filter(event);
+
+ // then
+ assertThat(result, equalTo(Result.NEUTRAL));
+ }
+
+ @Test
+ public void shouldNotFilterNonCommandLogEvent() {
+ // given
+ Message message = mockMessage(OTHER_COMMAND);
+ LogEvent event = Mockito.mock(LogEvent.class);
+ when(event.getMessage()).thenReturn(message);
+
+ // when
+ Result result = log4JFilter.filter(event);
+
+ // then
+ assertThat(result, equalTo(Result.NEUTRAL));
+ }
+
+ @Test
+ public void shouldNotFilterLogEventWithNullMessage() {
+ // given
+ Message message = mockMessage(null);
+ LogEvent event = Mockito.mock(LogEvent.class);
+ when(event.getMessage()).thenReturn(message);
+
+ // when
+ Result result = log4JFilter.filter(event);
+
+ // then
+ assertThat(result, equalTo(Result.NEUTRAL));
+ }
+
+ @Test
+ public void shouldNotFilterWhenLogEventIsNull() {
+ // given / when
+ Result result = log4JFilter.filter(null);
+
+ // then
+ assertThat(result, equalTo(Result.NEUTRAL));
+ }
+
+ // ----------
+ // Test filter(Logger, Level, Marker, String, Object...)
+ // ----------
+ @Test
+ public void shouldFilterSensitiveStringMessage() {
+ // given / when
+ Result result = log4JFilter.filter(null, null, null, SENSITIVE_COMMAND, new Object[0]);
+
+ // then
+ assertThat(result, equalTo(Result.DENY));
+ }
+
+ @Test
+ public void shouldNotFilterNormalStringMessage() {
+ // given / when
+ Result result = log4JFilter.filter(null, null, null, NORMAL_COMMAND, new Object[0]);
+
+ // then
+ assertThat(result, equalTo(Result.NEUTRAL));
+ }
+
+ @Test
+ public void shouldNotFilterNonCommandStringMessage() {
+ // given / when
+ Result result = log4JFilter.filter(null, null, null, OTHER_COMMAND, new Object[0]);
+
+ // then
+ assertThat(result, equalTo(Result.NEUTRAL));
+ }
+
+ @Test
+ public void shouldReturnNeutralForNullMessage() {
+ // given / when
+ Result result = log4JFilter.filter(null, null, null, null, new Object[0]);
+
+ // then
+ assertThat(result, equalTo(Result.NEUTRAL));
+ }
+
+ // --------
+ // Test filter(Logger, Level, Marker, Object, Throwable)
+ // --------
+ @Test
+ public void shouldFilterSensitiveObjectMessage() {
+ // given / when
+ Result result = log4JFilter.filter(null, null, null, SENSITIVE_COMMAND, new Exception());
+
+ // then
+ assertThat(result, equalTo(Result.DENY));
+ }
+
+ @Test
+ public void shouldNotFilterNullObjectParam() {
+ // given / when
+ Result result = log4JFilter.filter(null, null, null, (Object) null, new Exception());
+
+ // then
+ assertThat(result, equalTo(Result.NEUTRAL));
+ }
+
+ @Test
+ public void shouldNotFilterIrrelevantMessage() {
+ // given / when
+ Result result = log4JFilter.filter(null, null, null, OTHER_COMMAND, new Exception());
+
+ // then
+ assertThat(result, equalTo(Result.NEUTRAL));
+ }
+
+ @Test
+ public void shouldNotFilterNonSensitiveCommand() {
+ // given / when
+ Result result = log4JFilter.filter(null, null, null, NORMAL_COMMAND, new Exception());
+
+ // then
+ assertThat(result, equalTo(Result.NEUTRAL));
+ }
+
+ // --------
+ // Test filter(Logger, Level, Marker, Message, Throwable)
+ // --------
+ @Test
+ public void shouldFilterSensitiveMessage() {
+ // given
+ Message message = mockMessage(SENSITIVE_COMMAND);
+
+ // when
+ Result result = log4JFilter.filter(null, null, null, message, new Exception());
+
+ // then
+ assertThat(result, equalTo(Result.DENY));
+ }
+
+ @Test
+ public void shouldNotFilterNonSensitiveMessage() {
+ // given
+ Message message = mockMessage(NORMAL_COMMAND);
+
+ // when
+ Result result = log4JFilter.filter(null, null, null, message, new Exception());
+
+ // then
+ assertThat(result, equalTo(Result.NEUTRAL));
+ }
+
+ @Test
+ public void shouldNotFilterNonCommandMessage() {
+ // given
+ Message message = mockMessage(OTHER_COMMAND);
+
+ // when
+ Result result = log4JFilter.filter(null, null, null, message, new Exception());
+
+ // then
+ assertThat(result, equalTo(Result.NEUTRAL));
+ }
+
+ @Test
+ public void shouldNotFilterNullMessage() {
+ // given / when
+ Result result = log4JFilter.filter(null, null, null, (Message) null, new Exception());
+
+ // then
+ assertThat(result, equalTo(Result.NEUTRAL));
+ }
+
+ /**
+ * Mocks a {@link Message} object and makes it return the given formatted message.
+ *
+ * @param formattedMessage the formatted message the mock should return
+ * @return Message mock
+ */
+ private static Message mockMessage(String formattedMessage) {
+ Message message = Mockito.mock(Message.class);
+ when(message.getFormattedMessage()).thenReturn(formattedMessage);
+ return message;
+ }
+
+}