This is an automated email from the ASF dual-hosted git repository.

smolnar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git


The following commit(s) were added to refs/heads/master by this push:
     new eac2778  KNOX-2147 - Mask username/password in case we display call 
history and keep them safely (by setting proper file permissions) in JSON file 
(#217)
eac2778 is described below

commit eac277811863f7b41b6d9c8a312f299c3862fca8
Author: Sandor Molnar <[email protected]>
AuthorDate: Thu Dec 19 09:24:34 2019 +0100

    KNOX-2147 - Mask username/password in case we display call history and keep 
them safely (by setting proper file permissions) in JSON file (#217)
---
 .../shell/table/JDBCKnoxShellTableBuilder.java     | 15 +++---
 .../knox/gateway/shell/table/KnoxShellTable.java   | 22 ++++++--
 .../gateway/shell/table/KnoxShellTableCall.java    |  5 ++
 .../shell/table/KnoxShellTableJSONSerializer.java  | 60 +++++++++++++++++++---
 .../gateway/shell/table/KnoxShellTableTest.java    | 41 +++++++++++++++
 5 files changed, 126 insertions(+), 17 deletions(-)

diff --git 
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/JDBCKnoxShellTableBuilder.java
 
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/JDBCKnoxShellTableBuilder.java
index a2e5d7f..0543044 100644
--- 
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/JDBCKnoxShellTableBuilder.java
+++ 
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/JDBCKnoxShellTableBuilder.java
@@ -26,6 +26,8 @@ import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.Locale;
 
+import org.apache.commons.lang3.StringUtils;
+
 public class JDBCKnoxShellTableBuilder extends KnoxShellTableBuilder {
 
   private String connectionUrl;
@@ -107,15 +109,12 @@ public class JDBCKnoxShellTableBuilder extends 
KnoxShellTableBuilder {
     return this.table;
   }
 
-  public Connection createConnection() throws SQLException {
-    Connection con = null;
-    if (username != null && pass != null) {
-      con = DriverManager.getConnection(connectionUrl, username, pass);
-    }
-    else {
-      con = DriverManager.getConnection(connectionUrl);
+  private Connection createConnection() throws SQLException {
+    if (StringUtils.isNotBlank(username) && pass != null) {
+      return DriverManager.getConnection(connectionUrl, username, pass);
+    } else {
+      return DriverManager.getConnection(connectionUrl);
     }
-    return con;
   }
 
   // added this as a private method so that KnoxShellTableHistoryAspect will 
not
diff --git 
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTable.java
 
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTable.java
index 560ac20..591f8aa 100644
--- 
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTable.java
+++ 
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTable.java
@@ -19,12 +19,15 @@ package org.apache.knox.gateway.shell.table;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.swing.SortOrder;
 import com.fasterxml.jackson.annotation.JsonFilter;
+
 import org.apache.commons.math3.stat.StatUtils;
 
 /**
@@ -307,7 +310,12 @@ public class KnoxShellTable {
   }
 
   public List<KnoxShellTableCall> getCallHistoryList() {
-    return KnoxShellTableCallHistory.getInstance().getCallHistory(id);
+    final List<KnoxShellTableCall> sanitizedCallHistoryList = new 
LinkedList<>();
+    KnoxShellTableCallHistory.getInstance().getCallHistory(id).forEach(call -> 
{
+      final Map<Object, Class<?>> params = call.hasSensitiveData() ? 
Collections.singletonMap("***", String.class) : call.getParams();
+      sanitizedCallHistoryList.add(new 
KnoxShellTableCall(call.getInvokerClass(), call.getMethod(), 
call.isBuilderMethod(), params));
+    });
+    return sanitizedCallHistoryList;
   }
 
   public String getCallHistory() {
@@ -410,11 +418,19 @@ public class KnoxShellTable {
   }
 
   public String toJSON() {
-    return toJSON(true);
+    return toJSON((String) null);
   }
 
   public String toJSON(boolean data) {
-    return KnoxShellTableJSONSerializer.serializeKnoxShellTable(this, data);
+    return toJSON(data, null);
+  }
+
+  public String toJSON(String path) {
+    return toJSON(true, path);
+  }
+
+  public String toJSON(boolean data, String path) {
+    return KnoxShellTableJSONSerializer.serializeKnoxShellTable(this, data, 
path);
   }
 
   public String toCSV() {
diff --git 
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableCall.java
 
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableCall.java
index 6ef5c2d..61bf6cd 100644
--- 
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableCall.java
+++ 
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableCall.java
@@ -62,6 +62,11 @@ class KnoxShellTableCall {
   }
 
   @JsonIgnore
+  boolean hasSensitiveData() {
+    return "username".equals(getMethod()) || "pwd".equals(getMethod());
+  }
+
+  @JsonIgnore
   Class<?>[] getParameterTypes() {
     final List<Class<?>> parameterTypes = new ArrayList<>(params.size());
     if (KNOX_SHELL_TABLE_FILTER_TYPE.equals(invokerClass) && builderMethod) {
diff --git 
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableJSONSerializer.java
 
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableJSONSerializer.java
index 7ea1ce3..24850e2 100644
--- 
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableJSONSerializer.java
+++ 
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/table/KnoxShellTableJSONSerializer.java
@@ -17,10 +17,17 @@
  */
 package org.apache.knox.gateway.shell.table;
 
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.Locale;
 
+import org.apache.commons.lang3.StringUtils;
+import org.apache.knox.gateway.shell.KnoxShellException;
 import org.apache.knox.gateway.util.JsonUtils;
 
 import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
@@ -51,15 +58,56 @@ class KnoxShellTableJSONSerializer {
    *          if this is <code>true</code> the underlying JSON serializer will
    *          output the table's content; otherwise the table's
    *          <code>callHistory</code> will be serilized
+   * @param filePath
+   *          if set, the JSON result will be written into the given file
+   *          (creating if not exists; overwritten if exists)
    * @return the serialized table in JSON format
    */
-  static String serializeKnoxShellTable(KnoxShellTable table, boolean data) {
-    SimpleFilterProvider filterProvider = new SimpleFilterProvider();
-    if (data) {
-      filterProvider.addFilter("knoxShellTableFilter", 
SimpleBeanPropertyFilter.filterOutAllExcept("headers", "rows", "title", "id"));
+  static String serializeKnoxShellTable(KnoxShellTable table, boolean data, 
String filePath) {
+    if (StringUtils.isNotBlank(filePath)) {
+      return saveTableInFile(table, data, filePath);
     } else {
-      filterProvider.addFilter("knoxShellTableFilter", 
SimpleBeanPropertyFilter.filterOutAllExcept("callHistoryList"));
+      final SimpleFilterProvider filterProvider = new SimpleFilterProvider();
+      if (data) {
+        filterProvider.addFilter("knoxShellTableFilter", 
SimpleBeanPropertyFilter.filterOutAllExcept("headers", "rows", "title", "id"));
+      } else {
+        filterProvider.addFilter("knoxShellTableFilter", 
SimpleBeanPropertyFilter.filterOutAllExcept("callHistoryList"));
+      }
+      return JsonUtils.renderAsJsonString(table, filterProvider, 
JSON_DATE_FORMAT.get());
     }
-    return JsonUtils.renderAsJsonString(table, filterProvider, 
JSON_DATE_FORMAT.get());
+  }
+
+  private static String saveTableInFile(KnoxShellTable table, boolean data, 
String filePath) {
+    try {
+      final String jsonResult;
+      if (data) {
+        final SimpleFilterProvider filterProvider = new SimpleFilterProvider();
+        filterProvider.addFilter("knoxShellTableFilter", 
SimpleBeanPropertyFilter.filterOutAllExcept("headers", "rows", "title", "id"));
+        jsonResult = JsonUtils.renderAsJsonString(table, filterProvider, 
JSON_DATE_FORMAT.get());
+      } else {
+        jsonResult = 
JsonUtils.renderAsJsonString(KnoxShellTableCallHistory.getInstance().getCallHistory(table.id),
 null, JSON_DATE_FORMAT.get());
+      }
+      final Path jsonFilePath = Paths.get(filePath);
+      if (!Files.exists(jsonFilePath.getParent())) {
+        Files.createDirectories(jsonFilePath.getParent());
+      }
+      Files.deleteIfExists(jsonFilePath);
+      Files.createFile(jsonFilePath);
+      setPermissions(jsonFilePath);
+      Files.write(jsonFilePath, jsonResult.getBytes(StandardCharsets.UTF_8));
+      return "Successfully saved into " + filePath;
+    } catch (IOException e) {
+      throw new KnoxShellException("Error while saving KnoxShellTable JSON 
into " + filePath, e);
+    }
+  }
+
+  private static void setPermissions(Path path) throws IOException {
+    // clear all flags for everybody
+    path.toFile().setReadable(false, false);
+    path.toFile().setWritable(false, false);
+    path.toFile().setExecutable(false, false);
+    // allow owners to read/write
+    path.toFile().setReadable(true, true);
+    path.toFile().setWritable(true, true);
   }
 }
diff --git 
a/gateway-shell/src/test/java/org/apache/knox/gateway/shell/table/KnoxShellTableTest.java
 
b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/table/KnoxShellTableTest.java
index 84d1723..470cf4f 100644
--- 
a/gateway-shell/src/test/java/org/apache/knox/gateway/shell/table/KnoxShellTableTest.java
+++ 
b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/table/KnoxShellTableTest.java
@@ -18,6 +18,7 @@
 package org.apache.knox.gateway.shell.table;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Collections.singletonMap;
 import static org.apache.commons.io.FileUtils.readFileToString;
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.expect;
@@ -33,8 +34,11 @@ import static org.junit.Assert.assertTrue;
 import java.io.File;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermissions;
 import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.ResultSetMetaData;
@@ -42,6 +46,8 @@ import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import javax.swing.SortOrder;
 
@@ -202,6 +208,15 @@ public class KnoxShellTableTest {
 
     KnoxShellTable table2 = KnoxShellTable.builder().json().fromJson(json);
     assertEquals(table.toString(), table2.toString());
+
+    final Path jsonPath = Paths.get(testFolder.newFolder().getAbsolutePath(), 
"testJson.json");
+    table.toJSON(jsonPath.toString());
+
+    final PosixFileAttributes jsonPathAttributes = 
Files.readAttributes(jsonPath, PosixFileAttributes.class);
+    assertEquals("rw-------", 
PosixFilePermissions.toString(jsonPathAttributes.permissions()));
+
+    KnoxShellTable table3 = 
KnoxShellTable.builder().json().path(jsonPath.toString());
+    assertEquals(table.toString(), table3.toString());
   }
 
   @Test
@@ -434,6 +449,32 @@ public class KnoxShellTableTest {
     return derbyDatabase;
   }
 
+  @Test
+  public void 
shouldMaskUserNameAndPasswordParametersWhenConnectingToDBUsingJDBCBuilder() 
throws Exception {
+    final KnoxShellTable table = new KnoxShellTable();
+    // it's quite hard to integrate AspectJ weaving together with JUnit so we
+    // manually build up the history
+    saveCall(table.id, 
"org.apache.knox.gateway.shell.table.KnoxShellTableBuilder", "jdbc", false, 
Collections.emptyMap());
+    saveCall(table.id, 
"org.apache.knox.gateway.shell.table.JDBCKnoxShellTableBuilder", "driver", 
false, singletonMap("org.apache.derby.jdbc.EmbeddedDriver", String.class));
+    saveCall(table.id, 
"org.apache.knox.gateway.shell.table.JDBCKnoxShellTableBuilder", "username", 
false, singletonMap("myUserName", String.class));
+    saveCall(table.id, 
"org.apache.knox.gateway.shell.table.JDBCKnoxShellTableBuilder", "pwd", false, 
singletonMap("myP4ssW0rd", String.class));
+    saveCall(table.id, 
"org.apache.knox.gateway.shell.table.JDBCKnoxShellTableBuilder", "connectTo", 
false, singletonMap("myDBConnectionUrl", String.class));
+    saveCall(table.id, 
"org.apache.knox.gateway.shell.table.JDBCKnoxShellTableBuilder", "sql", true, 
singletonMap("SELECT 1 FROM DUAL", String.class));
+    final AtomicBoolean foundSensitiveData = new AtomicBoolean(false);
+    table.getCallHistoryList().forEach(call -> {
+      if (call.hasSensitiveData()) {
+        assertEquals(1, call.getParams().size());
+        assertTrue(call.getParams().containsKey("***"));
+        foundSensitiveData.set(true);
+      }
+    });
+    assertTrue(foundSensitiveData.get());
+  }
+
+  private void saveCall(long id, String invokerClass, String method, boolean 
builderMethod, Map<Object, Class<?>>params) {
+    KnoxShellTableCallHistory.getInstance().saveCall(id, new 
KnoxShellTableCall(invokerClass, method, builderMethod, params));
+  }
+
   @Test (expected = IllegalArgumentException.class)
   public void testConversion() throws IllegalArgumentException {
       KnoxShellTable TABLE = new KnoxShellTable();

Reply via email to