From 7726ce4569a287e32fbda6f01ad2846ada909436 Mon Sep 17 00:00:00 2001 From: Nathan Voxland Date: Wed, 6 May 2020 13:59:53 -0500 Subject: [PATCH] Refactored classloader management to not change the Thread's context classloader Auto-add jar files found in configured classpath directories Improved on-screen help for jdbc url and classpath DAT-4624 SECURITY-519 --- .../plugins/liquibase/common/Util.java | 94 ++++++++----------- .../evaluator/AbstractLiquibaseBuilder.java | 26 ++--- .../liquibase-common-config.jelly | 4 +- .../AbstractLiquibaseStep/config.jelly | 4 +- 4 files changed, 54 insertions(+), 74 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/liquibase/common/Util.java b/src/main/java/org/jenkinsci/plugins/liquibase/common/Util.java index ec43c9d..4a09dc8 100755 --- a/src/main/java/org/jenkinsci/plugins/liquibase/common/Util.java +++ b/src/main/java/org/jenkinsci/plugins/liquibase/common/Util.java @@ -1,8 +1,14 @@ package org.jenkinsci.plugins.liquibase.common; +import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; import hudson.FilePath; import liquibase.changelog.ChangeSet; import liquibase.changelog.DatabaseChangeLog; +import liquibase.util.StringUtils; +import org.jenkinsci.plugins.liquibase.exception.LiquibaseRuntimeException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; @@ -10,23 +16,13 @@ import java.net.URI; import java.net.URL; import java.net.URLClassLoader; +import java.nio.file.Path; import java.nio.file.Paths; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.sql.Driver; import java.sql.DriverManager; import java.sql.SQLException; - -import javax.annotation.Nullable; - -import org.jenkinsci.plugins.liquibase.exception.LiquibaseRuntimeException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.base.Function; -import com.google.common.base.Splitter; -import com.google.common.base.Strings; -import com.google.common.collect.Iterables; +import java.util.ArrayList; +import java.util.List; public class Util { private static final Logger LOG = LoggerFactory.getLogger(Util.class); @@ -47,62 +43,52 @@ public static String formatChangeset(ChangeSet changeSet) { return filePath + "::" + changeSetName.replace(filePath + "::", ""); } - public static void registerDatabaseDriver(String driverName, String classpath) - throws InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException { + public static void registerDatabaseDriver(String driverName, ClassLoader liquibaseClassLoader) throws SQLException { Driver driver; - if (!Strings.isNullOrEmpty(classpath)) { - Driver actualDriver = - (Driver) Class.forName(driverName, true, Thread.currentThread().getContextClassLoader()) - .newInstance(); + try { + Driver actualDriver = (Driver) Class.forName(driverName, true, liquibaseClassLoader).getDeclaredConstructor().newInstance(); driver = new DriverShim(actualDriver); - } else { - driver = (Driver) Class.forName(driverName).newInstance(); - } - DriverManager.registerDriver(driver); + DriverManager.registerDriver(driver); + } catch (ReflectiveOperationException e) { + throw new SQLException("Cannot create driver " + driverName + ": " + e.getMessage(), e); + } } - public static void addClassloader(boolean isUnix, final FilePath workspace, String classpath) { + public static ClassLoader createClassLoader(boolean isUnix, final FilePath workspace, String classpath) { String separator; if (isUnix) { separator = ":"; } else { separator = ";"; } + + List classpathUrls = new ArrayList<>(); Iterable classPathElements = Splitter.on(separator).trimResults().split(classpath); - final Iterable urlIterable = Iterables.transform(classPathElements, new Function() { - @Override - public URL apply(@Nullable String filePath) { - URL url = null; - if (filePath != null) { - try { - if (Paths.get(filePath).isAbsolute()) { - url = new File(filePath).toURI().toURL(); - } else { - URI workspaceUri = workspace.toURI(); - File workspace = new File(workspaceUri); - url = new File(workspace, filePath).toURI().toURL(); - } - } catch (MalformedURLException e) { - LOG.warn("Unable to transform classpath element " + filePath, e); - } catch (InterruptedException e) { - throw new LiquibaseRuntimeException("Error during database driver resolution", e); - } catch (IOException e) { - throw new LiquibaseRuntimeException("Error during database driver resolution", e); - } - } - return url; + + for (String filePath : classPathElements) { + filePath = StringUtils.trimToNull(filePath); + if (filePath == null) { + continue; } - }); - URLClassLoader urlClassLoader = AccessController.doPrivileged(new PrivilegedAction() { - @Override - public URLClassLoader run() { - return new URLClassLoader(Iterables.toArray(urlIterable, URL.class), - Thread.currentThread().getContextClassLoader()); + //jenkins FilePath prefers unix style, even on windows + filePath = filePath.replace("\\", "/"); + + FilePath file = workspace.child(filePath); + try { + + if (file.isDirectory()) { + for (FilePath jarFile : file.list("*.jar")) { + classpathUrls.add(jarFile.toURI().toURL()); + } + } + classpathUrls.add(file.toURI().toURL()); + } catch (Exception e) { + LOG.warn("Error handling classpath " + file+": "+e.getMessage(), e); } - }); - Thread.currentThread().setContextClassLoader(urlClassLoader); + } + return new URLClassLoader(Iterables.toArray(classpathUrls, URL.class), Util.class.getClassLoader()); } } diff --git a/src/main/java/org/jenkinsci/plugins/liquibase/evaluator/AbstractLiquibaseBuilder.java b/src/main/java/org/jenkinsci/plugins/liquibase/evaluator/AbstractLiquibaseBuilder.java index 0e78328..a4cc7f1 100644 --- a/src/main/java/org/jenkinsci/plugins/liquibase/evaluator/AbstractLiquibaseBuilder.java +++ b/src/main/java/org/jenkinsci/plugins/liquibase/evaluator/AbstractLiquibaseBuilder.java @@ -27,7 +27,6 @@ import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; -import java.util.List; import java.util.Map; import java.util.Properties; @@ -149,12 +148,14 @@ public Liquibase createLiquibase(Run build, EnvVars environment = build.getEnvironment(listener); try { + ClassLoader liquibaseClassLoader = this.getClass().getClassLoader(); + if (!Strings.isNullOrEmpty(resolvedClasspath)) { - Util.addClassloader(launcher.isUnix(), workspace, resolvedClasspath); + liquibaseClassLoader = Util.createClassLoader(launcher.isUnix(), workspace, resolvedClasspath); } - JdbcConnection jdbcConnection = createJdbcConnection(configProperties); + JdbcConnection jdbcConnection = createJdbcConnection(configProperties, liquibaseClassLoader); Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(jdbcConnection); - ResourceAccessor resourceAccessor = createResourceAccessor(workspace, environment, resolveMacros); + ResourceAccessor resourceAccessor = createResourceAccessor(workspace, environment, resolveMacros, liquibaseClassLoader); String changeLogFile = getProperty(configProperties, LiquibaseProperty.CHANGELOG_FILE); liquibase = new Liquibase(changeLogFile, resourceAccessor, database); @@ -172,7 +173,8 @@ public Liquibase createLiquibase(Run build, private ResourceAccessor createResourceAccessor(FilePath workspace, Map environment, - boolean resolveMacros) { + boolean resolveMacros, + ClassLoader liquibaseClassLoader) { String resolvedBasePath; if (resolveMacros) { resolvedBasePath = hudson.Util.replaceMacro(basePath, environment); @@ -188,8 +190,7 @@ private ResourceAccessor createResourceAccessor(FilePath workspace, ResourceAccessor filePathAccessor = new FilePathAccessor(filePath); return new CompositeResourceAccessor(filePathAccessor, - new ClassLoaderResourceAccessor(Thread.currentThread().getContextClassLoader()), - new ClassLoaderResourceAccessor(ClassLoader.getSystemClassLoader()) + new ClassLoaderResourceAccessor(liquibaseClassLoader) ); } @@ -213,25 +214,18 @@ protected static void populateChangeLogParameters(Liquibase liquibase, } } - private static JdbcConnection createJdbcConnection(Properties configProperties) { + private static JdbcConnection createJdbcConnection(Properties configProperties, ClassLoader liquibaseClassLoader) { Connection connection; String dbUrl = getProperty(configProperties, LiquibaseProperty.URL); final String driverName = DatabaseFactory.getInstance().findDefaultDriver(dbUrl); try { - Util.registerDatabaseDriver(driverName, - configProperties.getProperty(LiquibaseProperty.CLASSPATH.propertyName())); + Util.registerDatabaseDriver(driverName, liquibaseClassLoader); String userName = getProperty(configProperties, LiquibaseProperty.USERNAME); String password = getProperty(configProperties, LiquibaseProperty.PASSWORD); connection = DriverManager.getConnection(dbUrl, userName, password); } catch (SQLException e) { throw new RuntimeException( "Error getting database connection using driver " + driverName + " using url '" + dbUrl + "'", e); - } catch (InstantiationException e) { - throw new RuntimeException("Error registering database driver " + driverName, e); - } catch (IllegalAccessException e) { - throw new RuntimeException("Error registering database driver " + driverName, e); - } catch (ClassNotFoundException e) { - throw new RuntimeException("Error registering database driver " + driverName, e); } return new JdbcConnection(connection); } diff --git a/src/main/resources/org/jenkinsci/plugins/liquibase/evaluator/AbstractLiquibaseBuilder/liquibase-common-config.jelly b/src/main/resources/org/jenkinsci/plugins/liquibase/evaluator/AbstractLiquibaseBuilder/liquibase-common-config.jelly index ff35253..bfdfcbb 100644 --- a/src/main/resources/org/jenkinsci/plugins/liquibase/evaluator/AbstractLiquibaseBuilder/liquibase-common-config.jelly +++ b/src/main/resources/org/jenkinsci/plugins/liquibase/evaluator/AbstractLiquibaseBuilder/liquibase-common-config.jelly @@ -5,7 +5,7 @@ xmlns:c="/lib/credentials"> - + @@ -35,7 +35,7 @@ + description="Classpath containing migration files and JDBC Driver. Can be relative to working directory or absolute. Jar files in listed directories are automatically added. Drivers for PostgreSQL, Mysql, H2, and Hsql come pre-bundled with this plugin."> - + @@ -35,7 +35,7 @@ + description="Classpath containing migration files and JDBC Driver. Can be relative to working directory or absolute. Jar files in listed directories are automatically added. Drivers for PostgreSQL, Mysql, H2, and Hsql come pre-bundled with this plugin.">