KNOX-2626. Support MySQL in JDBCTokenStateService (amagyar) (#464)

Co-authored-by: zeroflag <amagyar@cloudera.com>
diff --git a/gateway-server/pom.xml b/gateway-server/pom.xml
index a393cf4..430a1e2 100644
--- a/gateway-server/pom.xml
+++ b/gateway-server/pom.xml
@@ -405,6 +405,10 @@
             <groupId>org.apache.derby</groupId>
             <artifactId>derbyclient</artifactId>
         </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
 
         <!-- ********** ********** ********** ********** ********** ********** -->
         <!-- ********** Test Dependencies                           ********** -->
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/util/JDBCUtils.java b/gateway-server/src/main/java/org/apache/knox/gateway/util/JDBCUtils.java
index b2e6068..a44823f 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/util/JDBCUtils.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/util/JDBCUtils.java
@@ -17,8 +17,8 @@
  */
 package org.apache.knox.gateway.util;
 
-import javax.sql.DataSource;
-
+import com.mysql.cj.conf.PropertyDefinitions;
+import com.mysql.cj.jdbc.MysqlDataSource;
 import org.apache.derby.jdbc.ClientDataSource;
 import org.apache.knox.gateway.config.GatewayConfig;
 import org.apache.knox.gateway.services.security.AliasService;
@@ -27,18 +27,24 @@
 import org.postgresql.jdbc.SslMode;
 import org.postgresql.ssl.NonValidatingFactory;
 
+import javax.sql.DataSource;
+import java.sql.SQLException;
+
 public class JDBCUtils {
   public static final String POSTGRESQL_DB_TYPE = "postgresql";
+  public static final String MYSQL_DB_TYPE = "mysql";
   public static final String DERBY_DB_TYPE = "derbydb";
   public static final String DATABASE_USER_ALIAS_NAME = "gateway_database_user";
   public static final String DATABASE_PASSWORD_ALIAS_NAME = "gateway_database_password";
   public static final String DATABASE_TRUSTSTORE_PASSWORD_ALIAS_NAME = "gateway_database_ssl_truststore_password";
 
-  public static DataSource getDataSource(GatewayConfig gatewayConfig, AliasService aliasService) throws AliasServiceException {
+  public static DataSource getDataSource(GatewayConfig gatewayConfig, AliasService aliasService) throws AliasServiceException, SQLException {
     if (POSTGRESQL_DB_TYPE.equalsIgnoreCase(gatewayConfig.getDatabaseType())) {
       return createPostgresDataSource(gatewayConfig, aliasService);
     } else if (DERBY_DB_TYPE.equalsIgnoreCase(gatewayConfig.getDatabaseType())) {
       return createDerbyDatasource(gatewayConfig, aliasService);
+    } else if (MYSQL_DB_TYPE.equalsIgnoreCase(gatewayConfig.getDatabaseType())) {
+      return createMySqlDataSource(gatewayConfig, aliasService);
     }
     throw new IllegalArgumentException("Invalid database type: " + gatewayConfig.getDatabaseType());
   }
@@ -81,6 +87,36 @@
     return derbyDatasource;
   }
 
+  private static DataSource createMySqlDataSource(GatewayConfig gatewayConfig, AliasService aliasService) throws AliasServiceException, SQLException {
+    MysqlDataSource dataSource = new MysqlDataSource();
+    if (gatewayConfig.getDatabaseConnectionUrl() != null) {
+      dataSource.setUrl(gatewayConfig.getDatabaseConnectionUrl());
+    } else {
+      dataSource.setDatabaseName(gatewayConfig.getDatabaseName());
+      dataSource.setServerName(gatewayConfig.getDatabaseHost());
+      dataSource.setPortNumber(gatewayConfig.getDatabasePort());
+      dataSource.setUser(getDatabaseUser(aliasService));
+      dataSource.setPassword(getDatabasePassword(aliasService));
+      configureMysqlSsl(gatewayConfig, aliasService, dataSource);
+    }
+    return dataSource;
+  }
+
+  private static void configureMysqlSsl(GatewayConfig gatewayConfig, AliasService aliasService, MysqlDataSource dataSource) throws AliasServiceException, SQLException {
+    if (gatewayConfig.isDatabaseSslEnabled()) {
+      dataSource.setUseSSL(true);
+      if (gatewayConfig.verifyDatabaseSslServerCertificate()) {
+        dataSource.setSslMode(PropertyDefinitions.SslMode.VERIFY_CA.name());
+        dataSource.setVerifyServerCertificate(true);
+        dataSource.setTrustCertificateKeyStoreType("JKS");
+        dataSource.setTrustCertificateKeyStoreUrl("file:"+ gatewayConfig.getDatabaseSslTruststoreFileName());
+        dataSource.setTrustCertificateKeyStorePassword(getDatabaseAlias(aliasService, DATABASE_TRUSTSTORE_PASSWORD_ALIAS_NAME));
+      } else {
+        dataSource.setVerifyServerCertificate(false);
+      }
+    }
+  }
+
   private static String getDatabaseUser(AliasService aliasService) throws AliasServiceException {
     return getDatabaseAlias(aliasService, DATABASE_USER_ALIAS_NAME);
   }
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/util/JDBCUtilsTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/util/JDBCUtilsTest.java
index f80ff47..4dbc109 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/util/JDBCUtilsTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/util/JDBCUtilsTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.assertTrue;
 
 
+import com.mysql.cj.jdbc.MysqlDataSource;
 import org.apache.derby.jdbc.ClientDataSource;
 import org.apache.knox.gateway.config.GatewayConfig;
 import org.apache.knox.gateway.services.security.AliasService;
@@ -45,7 +46,7 @@
   }
 
   @Test
-  public void postgresDataSourceShouldHaveProperConnectionProperties() throws AliasServiceException {
+  public void postgresDataSourceShouldHaveProperConnectionProperties() throws Exception {
     final GatewayConfig gatewayConfig = EasyMock.createNiceMock(GatewayConfig.class);
     final AliasService aliasService = EasyMock.createNiceMock(AliasService.class);
     setBasicPostgresExpectations(gatewayConfig, aliasService);
@@ -106,7 +107,7 @@
   }
 
   @Test
-  public void testGetPostgreSqlDatasourceFromJdbcConnectionUrl() throws AliasServiceException {
+  public void testGetPostgreSqlDatasourceFromJdbcConnectionUrl() throws Exception {
     final String connectionUrl = "jdbc:postgresql://postgresql_host:1234/testDb?user=smolnar&password=secret&ssl=true&sslmode=verify-ca&sslrootcert=/var/lib/knox/gateway/conf/postgresql/root.crt";
     final GatewayConfig gatewayConfig = EasyMock.createNiceMock(GatewayConfig.class);
     EasyMock.expect(gatewayConfig.getDatabaseType()).andReturn(JDBCUtils.POSTGRESQL_DB_TYPE).anyTimes();
@@ -134,7 +135,7 @@
   }
 
   @Test
-  public void derbyDataSourceShouldHaveProperConnectionProperties() throws AliasServiceException {
+  public void derbyDataSourceShouldHaveProperConnectionProperties() throws Exception {
     final GatewayConfig gatewayConfig = EasyMock.createNiceMock(GatewayConfig.class);
     EasyMock.expect(gatewayConfig.getDatabaseType()).andReturn(JDBCUtils.DERBY_DB_TYPE).anyTimes();
     EasyMock.expect(gatewayConfig.getDatabaseHost()).andReturn("localhost").anyTimes();
@@ -151,4 +152,47 @@
     assertEquals("user", dataSource.getUser());
     assertEquals("password", dataSource.getPassword());
   }
+
+  @Test
+  public void shouldReturnMySqlDataSource() throws Exception {
+    final GatewayConfig gatewayConfig = EasyMock.createNiceMock(GatewayConfig.class);
+    EasyMock.expect(gatewayConfig.getDatabaseType()).andReturn(JDBCUtils.MYSQL_DB_TYPE).anyTimes();
+    final AliasService aliasService = EasyMock.createNiceMock(AliasService.class);
+    EasyMock.expect(aliasService.getPasswordFromAliasForGateway(EasyMock.anyString())).andReturn(null).anyTimes();
+    EasyMock.replay(gatewayConfig, aliasService);
+    assertTrue(JDBCUtils.getDataSource(gatewayConfig, aliasService) instanceof MysqlDataSource);
+  }
+
+  @Test
+  public void testMysqlDataSourceShouldHaveProperConnectionProperties() throws Exception {
+    GatewayConfig gatewayConfig = EasyMock.createNiceMock(GatewayConfig.class);
+    AliasService aliasService = EasyMock.createNiceMock(AliasService.class);
+    EasyMock.expect(gatewayConfig.getDatabaseType()).andReturn(JDBCUtils.MYSQL_DB_TYPE).anyTimes();
+    EasyMock.expect(gatewayConfig.getDatabaseHost()).andReturn("localhost").anyTimes();
+    EasyMock.expect(gatewayConfig.getDatabasePort()).andReturn(5432).anyTimes();
+    EasyMock.expect(gatewayConfig.getDatabaseName()).andReturn("sampleDatabase");
+    EasyMock.expect(aliasService.getPasswordFromAliasForGateway(JDBCUtils.DATABASE_USER_ALIAS_NAME)).andReturn("user".toCharArray()).anyTimes();
+    EasyMock.expect(aliasService.getPasswordFromAliasForGateway(JDBCUtils.DATABASE_PASSWORD_ALIAS_NAME)).andReturn("password".toCharArray()).anyTimes();
+    EasyMock.replay(gatewayConfig, aliasService);
+    MysqlDataSource dataSource = (MysqlDataSource) JDBCUtils.getDataSource(gatewayConfig, aliasService);
+    assertEquals("localhost", dataSource.getServerName());
+    assertEquals(5432, dataSource.getPortNumber());
+    assertEquals("sampleDatabase", dataSource.getDatabaseName());
+    assertEquals("user", dataSource.getUser());
+    assertEquals("password", dataSource.getPassword());
+    assertTrue(dataSource.getUseSSL());
+    EasyMock.verify(gatewayConfig);
+  }
+
+  @Test
+  public void testGetMySqlDatasourceFromJdbcConnectionUrl() throws Exception {
+    String connectionUrl = "jdbc:mysql://mysql_host:1234/testDb?user=user&password=secret&ssl=true&sslmode=verify-ca&sslrootcert=/var/lib/knox/gateway/conf/postgresql/root.crt";
+    GatewayConfig gatewayConfig = EasyMock.createNiceMock(GatewayConfig.class);
+    EasyMock.expect(gatewayConfig.getDatabaseType()).andReturn(JDBCUtils.MYSQL_DB_TYPE).anyTimes();
+    EasyMock.expect(gatewayConfig.getDatabaseConnectionUrl()).andReturn(connectionUrl).anyTimes();
+    EasyMock.replay(gatewayConfig);
+    MysqlDataSource dataSource = (MysqlDataSource) JDBCUtils.getDataSource(gatewayConfig, null);
+    assertEquals(connectionUrl, dataSource.getUrl());
+    EasyMock.verify(gatewayConfig);
+  }
 }
diff --git a/pom.xml b/pom.xml
index 7fb8c80..d4d57fe 100644
--- a/pom.xml
+++ b/pom.xml
@@ -252,6 +252,7 @@
         <opensaml.version>3.4.5</opensaml.version>
         <pac4j.version>4.3.0</pac4j.version>
         <postgresql.version>42.2.19</postgresql.version>
+        <mysql.version>8.0.25</mysql.version>
         <protobuf.version>3.14.0</protobuf.version>
         <rest-assured.version>4.3.3</rest-assured.version>
         <shiro.version>1.7.0</shiro.version>
@@ -2085,7 +2086,11 @@
                     </exclusion>
                 </exclusions>
             </dependency>
-
+            <dependency>
+                <groupId>mysql</groupId>
+                <artifactId>mysql-connector-java</artifactId>
+                <version>${mysql.version}</version>
+            </dependency>
             <!-- pac4j Dependencies -->
             <dependency>
                 <groupId>org.pac4j</groupId>