From 4505e3ab1619f529bcee6917ff5884edd567aae3 Mon Sep 17 00:00:00 2001 From: Pavel Horal Date: Sat, 3 Feb 2024 18:14:27 +0100 Subject: [PATCH] Fix Windows build incompatibilities. #168 --- .gitattributes | 3 + openidm-launcher/pom.xml | 8 +-- .../AbstractOSGiFrameworkService.java | 5 +- .../commons/launcher/ConfigurationUtil.java | 52 +++++++------- .../launcher/OSGiFrameworkService.java | 41 ++++++------ .../launcher/support/DirectoryScanner.java | 60 +++++++++++++++++ .../launcher/support/GlobPathMatcher.java | 58 ++++++++++++++++ .../launcher/ConfigurationUtilTest.java | 43 ++++++------ .../launcher/OSGiFrameworkServiceTest.java | 13 ++-- .../launcher/support/GlobPathMatcherTest.java | 67 +++++++++++++++++++ openidm-maintenance/pom.xml | 2 +- .../upgrade/UpdateManagerImpl.java | 20 +++--- .../upgrade/UpdateManagerImplTest.java | 23 +++++-- .../maintenance/upgrade/ZipUtilsTest.java | 35 +++++++--- 14 files changed, 313 insertions(+), 117 deletions(-) create mode 100644 .gitattributes create mode 100644 openidm-launcher/src/main/java/org/forgerock/commons/launcher/support/DirectoryScanner.java create mode 100644 openidm-launcher/src/main/java/org/forgerock/commons/launcher/support/GlobPathMatcher.java create mode 100644 openidm-launcher/src/test/java/org/forgerock/commons/launcher/support/GlobPathMatcherTest.java diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..3e6f3acd59 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# Test resources sensitive to content length +openidm-external-rest/src/test/resources/*.html text eol=lf +openidm-external-rest/src/test/resources/*.xml text eol=lf diff --git a/openidm-launcher/pom.xml b/openidm-launcher/pom.xml index c54b0e6625..0eca67e8c4 100644 --- a/openidm-launcher/pom.xml +++ b/openidm-launcher/pom.xml @@ -13,7 +13,7 @@ information: "Portions copyright [year] [name of copyright owner]". Copyright 2012-2013 ForgeRock AS. - Portions Copyright 2020-2023 Wren Security. + Portions Copyright 2020-2024 Wren Security. --> 4.0.0 @@ -39,11 +39,6 @@ org.wrensecurity.commons forgerock-util - - org.codehaus.plexus - plexus-utils - 3.3.0 - args4j args4j @@ -99,7 +94,6 @@ com.googlecode.json-simple:* org.forgerock.commons:* org.wrensecurity.commons:* - org.codehaus.plexus:plexus-utils args4j:args4j diff --git a/openidm-launcher/src/main/java/org/forgerock/commons/launcher/AbstractOSGiFrameworkService.java b/openidm-launcher/src/main/java/org/forgerock/commons/launcher/AbstractOSGiFrameworkService.java index b3770a4eee..6380d22b9e 100644 --- a/openidm-launcher/src/main/java/org/forgerock/commons/launcher/AbstractOSGiFrameworkService.java +++ b/openidm-launcher/src/main/java/org/forgerock/commons/launcher/AbstractOSGiFrameworkService.java @@ -16,8 +16,8 @@ */ package org.forgerock.commons.launcher; +import java.io.IOException; import java.net.MalformedURLException; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -26,7 +26,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; - import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkEvent; @@ -53,7 +52,7 @@ public abstract class AbstractOSGiFrameworkService implements OSGiFramework { * @return list of startup bundle handlers. */ protected abstract List listBundleHandlers(BundleContext context) - throws MalformedURLException; + throws IOException, MalformedURLException; /** * @return map of configuration properties. diff --git a/openidm-launcher/src/main/java/org/forgerock/commons/launcher/ConfigurationUtil.java b/openidm-launcher/src/main/java/org/forgerock/commons/launcher/ConfigurationUtil.java index c62669424e..ca8b376d1d 100644 --- a/openidm-launcher/src/main/java/org/forgerock/commons/launcher/ConfigurationUtil.java +++ b/openidm-launcher/src/main/java/org/forgerock/commons/launcher/ConfigurationUtil.java @@ -12,22 +12,26 @@ * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2012-2013 ForgeRock AS. - * Portions Copyright 2020 Wren Security. + * Portions Copyright 2020-2024 Wren Security. */ package org.forgerock.commons.launcher; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.PathMatcher; import java.util.List; import java.util.Stack; import java.util.Vector; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; +import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; - -import org.codehaus.plexus.util.MatchPatterns; +import org.forgerock.commons.launcher.support.GlobPathMatcher; /** * A ConfigurationUtil class contains util methods used by @@ -58,19 +62,24 @@ public static Vector getJarFileListing(URL location, List includes, return files; // Empty. } - MatchPatterns includesPatterns = null; - MatchPatterns excludesPatterns = null; + List includesPatterns = null; + List excludesPatterns = null; + FileSystem fileSystem = FileSystems.getDefault(); if (includes == null) { // No includes supplied, so set it to 'matches all' - includesPatterns = MatchPatterns.from("**"); + includesPatterns = List.of(fileSystem.getPathMatcher("glob:**")); } else { - includesPatterns = MatchPatterns.from(includes); + includesPatterns = includes.stream() + .map(pattern -> fileSystem.getPathMatcher("glob:" + pattern)) + .collect(Collectors.toList()); } if (excludes == null) { - excludesPatterns = MatchPatterns.from(); + excludesPatterns = List.of(); } else { - excludesPatterns = MatchPatterns.from(excludes); + excludesPatterns = excludes.stream() + .map(pattern -> fileSystem.getPathMatcher("glob:" + pattern)) + .collect(Collectors.toList());; } JarInputStream inputStream = null; @@ -86,8 +95,8 @@ public static Vector getJarFileListing(URL location, List includes, if (jarEntry != null && !jarEntry.isDirectory()) { String fileName = jarEntry.getName(); - if (includesPatterns.matches(fileName, false) - && !excludesPatterns.matches(fileName, false)) { + if (includesPatterns.stream().anyMatch(pattern -> pattern.matches(Path.of(fileName))) + && !excludesPatterns.stream().anyMatch(pattern -> pattern.matches(Path.of(fileName)))) { files.add(new URL(location, fileName)); } } @@ -140,20 +149,8 @@ public static Vector getZipFileListing(URL location, List includes, } } - MatchPatterns includesPatterns = null; - MatchPatterns excludesPatterns = null; - - if (includes == null) { - // No includes supplied, so set it to 'matches all' - includesPatterns = MatchPatterns.from("**"); - } else { - includesPatterns = MatchPatterns.from(includes); - } - if (excludes == null) { - excludesPatterns = MatchPatterns.from(); - } else { - excludesPatterns = MatchPatterns.from(excludes); - } + GlobPathMatcher includeMatcher = new GlobPathMatcher(includes != null ? includes : List.of("**")); + GlobPathMatcher excludeMatcher = new GlobPathMatcher(excludes != null ? excludes : List.of()); ZipInputStream inputStream = null; try { @@ -167,9 +164,8 @@ public static Vector getZipFileListing(URL location, List includes, jarEntry = inputStream.getNextEntry(); if (jarEntry != null && !jarEntry.isDirectory()) { String fileName = jarEntry.getName(); - - if (includesPatterns.matches(fileName, false) - && !excludesPatterns.matches(fileName, false)) { + Path filePath = Path.of(fileName); + if (includeMatcher.matches(filePath) && !excludeMatcher.matches(filePath)) { files.add(new URL(base, fileName)); } } diff --git a/openidm-launcher/src/main/java/org/forgerock/commons/launcher/OSGiFrameworkService.java b/openidm-launcher/src/main/java/org/forgerock/commons/launcher/OSGiFrameworkService.java index 915edf9bbe..cc6918d5e4 100644 --- a/openidm-launcher/src/main/java/org/forgerock/commons/launcher/OSGiFrameworkService.java +++ b/openidm-launcher/src/main/java/org/forgerock/commons/launcher/OSGiFrameworkService.java @@ -12,7 +12,7 @@ * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2012-2013 ForgeRock AS. - * Portions Copyright 2020 Wren Security. + * Portions Copyright 2020-2024 Wren Security. */ package org.forgerock.commons.launcher; @@ -30,6 +30,7 @@ import java.net.URI; import java.net.URL; import java.net.URLDecoder; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.Dictionary; @@ -41,8 +42,7 @@ import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; - -import org.codehaus.plexus.util.DirectoryScanner; +import org.forgerock.commons.launcher.support.DirectoryScanner; import org.forgerock.json.JsonValue; import org.forgerock.json.JsonValueTraverseFunction; import org.json.simple.parser.JSONParser; @@ -433,7 +433,7 @@ public void setLauncherConfiguration(JsonValue launcherConfiguration) { @Override protected List listBundleHandlers(BundleContext context) - throws MalformedURLException { + throws IOException, MalformedURLException { JsonValue bundle = getLauncherConfiguration().get("bundle"); BundleHandlerBuilder defaultBuilder = BundleHandlerBuilder.newBuilder(bundle.get("default")); @@ -459,22 +459,15 @@ protected List listBundleHandlers(BundleContext context) File inputFile = getFileForPath(location, installDirectory); result.add(innerBuilder.build(inputFile.toURI().toURL())); } else { - DirectoryScanner scanner = new DirectoryScanner(); - scanner.setBasedir(getFileForPath(location, installDirectory)); - if (container.isDefined("includes")) { - List includes = container.get("includes").asList(String.class); - scanner.setIncludes(includes.toArray(new String[includes.size()])); - } - if (container.isDefined("excludes")) { - List includes = container.get("excludes").asList(String.class); - scanner.setExcludes(includes.toArray(new String[includes.size()])); - } - scanner.scan(); + List includes = container.get("includes").asList(String.class); + List excludes = container.get("excludes").asList(String.class); + + DirectoryScanner scanner = new DirectoryScanner(includes, excludes); - for (String bundleLocation : scanner.getIncludedFiles()) { + Path base = getFileForPath(location, installDirectory).toPath(); + for (Path bundleLocation : scanner.scan(base)) { BundleHandler newHandler = - innerBuilder.build(scanner.getBasedir().toURI().resolve( - bundleLocation.replaceAll("\\\\", "/")).toURL()); + innerBuilder.build(bundleLocation.toUri().toURL()); for (BundleHandler handler : result) { if (newHandler.getBundleUrl().equals(handler.getBundleUrl())) { if (newHandler.getActions().equals(handler.getActions()) @@ -521,8 +514,9 @@ protected void loadSystemProperties(JsonValue configuration, URI projectDirector Properties props = loadPropertyFile(projectDirectory, systemProperties.expect(String.class) .defaultTo(SYSTEM_PROPERTIES_FILE_VALUE).asString()); - if (props == null) + if (props == null) { return; + } // Perform variable substitution on specified properties. for (Enumeration e = props.propertyNames(); e.hasMoreElements();) { String name = (String) e.nextElement(); @@ -559,8 +553,9 @@ protected Map loadConfigProperties(JsonValue configuration, URI Properties props = loadPropertyFile(projectDirectory, systemProperties.expect(String.class) .defaultTo(CONFIG_PROPERTIES_FILE_VALUE).asString()); - if (props == null) + if (props == null) { return new HashMap(0); + } // Perform variable substitution on specified properties. systemProperties = transformer.apply(new JsonValue(props, null)); } @@ -595,8 +590,9 @@ protected Map loadBootProperties(JsonValue configuration, URI pr Properties props = loadPropertyFile(projectDirectory, bootProperties.expect(String.class) .defaultTo(BOOT_PROPERTIES_FILE_VALUE).asString()); - if (props == null) + if (props == null) { return new HashMap(0); + } // Perform variable substitution on specified properties. return transformer.apply(new JsonValue(props, null)).asMap(); } @@ -626,8 +622,9 @@ protected Properties loadPropertyFile(URI projectDirectory, String propertyFile) System.err.append("Main: Error loading properties from ").println(propertyFile); System.err.println("Main: " + ex); try { - if (is != null) + if (is != null) { is.close(); + } } catch (IOException ex2) { // Nothing we can do. } diff --git a/openidm-launcher/src/main/java/org/forgerock/commons/launcher/support/DirectoryScanner.java b/openidm-launcher/src/main/java/org/forgerock/commons/launcher/support/DirectoryScanner.java new file mode 100644 index 0000000000..a947a0beb9 --- /dev/null +++ b/openidm-launcher/src/main/java/org/forgerock/commons/launcher/support/DirectoryScanner.java @@ -0,0 +1,60 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.1.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.1.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2024 Wren Security + */ +package org.forgerock.commons.launcher.support; + +import java.io.IOException; +import java.nio.file.FileVisitOption; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * Simple file tree walker based on previously used Plexus Utils' {@code DirectoryScanner}. + * + *

+ * The implementation does not try to do anything clever to optimize path traversal (e.g. by prefix + * matching to prevent walking into directories that can not contain matching files). + */ +public class DirectoryScanner { + + private PathMatcher includes; + + private PathMatcher excludes; + + public DirectoryScanner(List includes, List excludes) { + this(includes != null ? new GlobPathMatcher(includes) : null, + excludes != null ? new GlobPathMatcher(excludes) : null); + } + + public DirectoryScanner(PathMatcher includes, PathMatcher excludes) { + this.includes = includes != null ? includes : new GlobPathMatcher("**"); + this.excludes = excludes != null ? excludes : new GlobPathMatcher(); + } + + public List scan(Path start) throws IOException { + return Files.walk(start, FileVisitOption.FOLLOW_LINKS) + .map(start::relativize) + .filter(includes::matches) + .filter(Predicate.not(excludes::matches)) + .filter(Predicate.not(Files::isDirectory)) + .map(start::resolve) + .collect(Collectors.toList()); + } + +} diff --git a/openidm-launcher/src/main/java/org/forgerock/commons/launcher/support/GlobPathMatcher.java b/openidm-launcher/src/main/java/org/forgerock/commons/launcher/support/GlobPathMatcher.java new file mode 100644 index 0000000000..1101d09094 --- /dev/null +++ b/openidm-launcher/src/main/java/org/forgerock/commons/launcher/support/GlobPathMatcher.java @@ -0,0 +1,58 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.1.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.1.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2024 Wren Security + */ +package org.forgerock.commons.launcher.support; + +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Glob based path matcher that emulates behavior of previously used {@code MatchPattern} from Plexus Utils. + * + *

+ * Matcher uses native JRE's path matcher provided by default {@code FileSystem} implementation. This means that + * the behavior might be platform dependent depending on the used patterns. + * + *

+ * CAUTION: Plexus Utils' {@code MatchPattern} was matching simple filename patterns even in subdirectories. + * This implementation is not matching subdirectories without path traversal wildcard **/. + */ +public class GlobPathMatcher implements PathMatcher { + + private final List matchers; + + public GlobPathMatcher(String... pattern) { + this(List.of(pattern)); + } + + public GlobPathMatcher(List patterns) { + var fileSystem = FileSystems.getDefault(); + this.matchers = patterns.stream() + // make directory wildcard pattern optional and the pattern case insensitive + .map(pattern -> pattern.replace("**/", "{**/,}").toLowerCase()) + .map(pattern -> fileSystem.getPathMatcher("glob:" + pattern)) + .collect(Collectors.toList()); + } + + @Override + public boolean matches(Path path) { + var normalized = Path.of(path.toString().toLowerCase()); + return matchers.stream().anyMatch(matcher -> matcher.matches(normalized)); + } + +} diff --git a/openidm-launcher/src/test/java/org/forgerock/commons/launcher/ConfigurationUtilTest.java b/openidm-launcher/src/test/java/org/forgerock/commons/launcher/ConfigurationUtilTest.java index a7eb059b47..2142614e4a 100644 --- a/openidm-launcher/src/test/java/org/forgerock/commons/launcher/ConfigurationUtilTest.java +++ b/openidm-launcher/src/test/java/org/forgerock/commons/launcher/ConfigurationUtilTest.java @@ -24,17 +24,19 @@ package org.forgerock.commons.launcher; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + import java.io.InputStream; import java.net.URL; -import java.util.Arrays; +import java.util.List; import java.util.Vector; - -import org.junit.Assert; import org.testng.annotations.Test; /** * A NAME does ... - * + * * @author Laszlo Hordos */ public class ConfigurationUtilTest { @@ -42,18 +44,18 @@ public class ConfigurationUtilTest { public void testGetZipFileListing() throws Exception { URL zip = ConfigurationUtilTest.class.getResource("/test2/bundles.zip"); Vector result = ConfigurationUtil.getZipFileListing(zip, null, null); - Assert.assertEquals("Find all files", 3, result.size()); + assertEquals(result.size(), 3, "Find all files"); for (URL file : result) { InputStream is = null; try { is = file.openConnection().getInputStream(); if (is != null) { - Assert.assertTrue("Stream is empty", is.available() > 0); + assertTrue(is.available() > 0, "Stream is empty"); } else { - Assert.fail("Can not read from " + file); + fail("Can not read from " + file); } } catch (Exception e) { - Assert.fail(e.getMessage()); + fail(e.getMessage()); } finally { if (null != is) { try { @@ -63,21 +65,14 @@ public void testGetZipFileListing() throws Exception { } } } - result = - ConfigurationUtil.getZipFileListing(zip, Arrays.asList(new String[] { "**/*jar" }), - null); - Assert.assertEquals("Find all jar files", 2, result.size()); - result = - ConfigurationUtil.getZipFileListing(zip, Arrays.asList(new String[] { "*jar" }), - null); - Assert.assertEquals("Find jar file in the root", 1, result.size()); - result = - ConfigurationUtil.getZipFileListing(zip, Arrays - .asList(new String[] { "bundle/*jar" }), null); - Assert.assertEquals("Find jar file in the bundle", 1, result.size()); - result = - ConfigurationUtil.getZipFileListing(zip, Arrays.asList(new String[] { "**/*jar" }), - Arrays.asList(new String[] { "bundle/*jar" })); - Assert.assertEquals("Find jar file in the root exclude the bundle", 1, result.size()); + result = ConfigurationUtil.getZipFileListing(zip, List.of("**/*.jar"), null); + assertEquals(result.size(), 2, "Find all jar files"); + result = ConfigurationUtil.getZipFileListing(zip, List.of("*.jar"), null); + assertEquals(result.size(), 1, "Find jar file in the root"); + result = ConfigurationUtil.getZipFileListing(zip, List.of("bundle/*.jar"), null); + assertEquals(result.size(), 1, "Find jar file in the bundle"); + result = ConfigurationUtil.getZipFileListing(zip, List.of("*.jar", "**/*.jar"), List.of("bundle/*.jar")); + assertEquals(result.size(), 1, "Find jar file in the root exclude the bundle"); } + } diff --git a/openidm-launcher/src/test/java/org/forgerock/commons/launcher/OSGiFrameworkServiceTest.java b/openidm-launcher/src/test/java/org/forgerock/commons/launcher/OSGiFrameworkServiceTest.java index 5bc0ec1e7a..ec7dcdef2d 100644 --- a/openidm-launcher/src/test/java/org/forgerock/commons/launcher/OSGiFrameworkServiceTest.java +++ b/openidm-launcher/src/test/java/org/forgerock/commons/launcher/OSGiFrameworkServiceTest.java @@ -12,20 +12,18 @@ * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2012-2013 ForgeRock AS. - * Portions Copyright 2020 Wren Security. + * Portions Copyright 2020-2024 Wren Security. */ package org.forgerock.commons.launcher; import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.Matchers.aMapWithSize; import static org.hamcrest.Matchers.hasEntry; -import static org.hamcrest.Matchers.hasSize; -import static org.junit.Assert.assertThat; import java.io.File; import java.net.URLDecoder; import java.util.concurrent.Semaphore; - +import org.hamcrest.MatcherAssert; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; import org.osgi.framework.Bundle; @@ -125,13 +123,14 @@ public void testBundles() throws Exception { Bundle[] installedBundles = service.getSystemBundle().getBundleContext().getBundles(); if ("test1".equals(testName)) { - Assert.assertEquals(4, installedBundles.length, "Only 4 bundles should be installed"); + Assert.assertEquals(installedBundles.length, 4, "Only 4 bundles should be installed"); } if ("test2".equals(testName)) { - Assert.assertEquals(6, installedBundles.length, "Only 6 bundles should be installed"); + Assert.assertEquals(installedBundles.length, 6, "Only 6 bundles should be installed"); } } + @SuppressWarnings("unchecked") @Test public void parserTestOk() throws Exception { OSGiFrameworkService testable = new OSGiFrameworkService(); @@ -150,7 +149,7 @@ public void parserTestOk() throws Exception { Assert.assertEquals(testable.getStorageDir(), "storage-location"); Assert.assertEquals(testable.isVerbose(), true); Assert.assertEquals(testable.isNewThread(), true); - assertThat(testable.getBootParameters(), allOf(aMapWithSize(2), + MatcherAssert.assertThat(testable.getBootParameters(), allOf(aMapWithSize(2), hasEntry("key1", "value1"), hasEntry("key2", "value2"))); } diff --git a/openidm-launcher/src/test/java/org/forgerock/commons/launcher/support/GlobPathMatcherTest.java b/openidm-launcher/src/test/java/org/forgerock/commons/launcher/support/GlobPathMatcherTest.java new file mode 100644 index 0000000000..502d93973a --- /dev/null +++ b/openidm-launcher/src/test/java/org/forgerock/commons/launcher/support/GlobPathMatcherTest.java @@ -0,0 +1,67 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.1.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.1.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2024 Wren Security + */ +package org.forgerock.commons.launcher.support; + +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.nio.file.Path; +import org.testng.annotations.Test; + +/** + * {@link GlobPathMatcher} test case. + * + *

+ * This class serves mainly as a template of what one can expect to work when matching path patterns. + */ +public class GlobPathMatcherTest { + + @Test + public void testSimpleMatch() { + var matcher = new GlobPathMatcher("hello.txt"); + assertTrue(matcher.matches(Path.of("hello.txt"))); + assertTrue(matcher.matches(Path.of("HELLO.TXT"))); + assertFalse(matcher.matches(Path.of("greetings/hello.txt"))); + assertFalse(matcher.matches(Path.of("goodbye.txt"))); + } + + @Test + public void testDirectoryMatch() { + var matcher = new GlobPathMatcher("**/hello.txt"); + assertTrue(matcher.matches(Path.of("hello.txt"))); + assertTrue(matcher.matches(Path.of("foo/hello.txt"))); + assertTrue(matcher.matches(Path.of("foo/bar/hello.txt"))); + assertFalse(matcher.matches(Path.of("foo/bar/goodbye.txt"))); + } + + @Test + public void testWildcardMatch() { + var matcher = new GlobPathMatcher("greetings/**/*.txt"); + assertTrue(matcher.matches(Path.of("greetings/hello.txt"))); + assertTrue(matcher.matches(Path.of("greetings/foo/hello.txt"))); + assertFalse(matcher.matches(Path.of("greetings/bar/hello.jar"))); + assertFalse(matcher.matches(Path.of("foo/bar/hello.txt"))); + } + + @Test + public void testCaseInsensitivity() { + var matcher = new GlobPathMatcher("foo/*.txt"); + assertTrue(matcher.matches(Path.of("foo/hello.txt"))); + assertTrue(matcher.matches(Path.of("FOO/hello.txt"))); + assertTrue(matcher.matches(Path.of("foo/hello.TXT"))); + } + +} diff --git a/openidm-maintenance/pom.xml b/openidm-maintenance/pom.xml index 9c51c809fd..0458088dfd 100644 --- a/openidm-maintenance/pom.xml +++ b/openidm-maintenance/pom.xml @@ -13,7 +13,7 @@ information: "Portions copyright [year] [name of copyright owner]". Copyright 2011-2016 ForgeRock AS. - Portions Copyright 2017-2022 Wren Security. + Portions Copyright 2017-2024 Wren Security. --> 4.0.0 diff --git a/openidm-maintenance/src/main/java/org/forgerock/openidm/maintenance/upgrade/UpdateManagerImpl.java b/openidm-maintenance/src/main/java/org/forgerock/openidm/maintenance/upgrade/UpdateManagerImpl.java index 225694bc16..43d796ccc5 100644 --- a/openidm-maintenance/src/main/java/org/forgerock/openidm/maintenance/upgrade/UpdateManagerImpl.java +++ b/openidm-maintenance/src/main/java/org/forgerock/openidm/maintenance/upgrade/UpdateManagerImpl.java @@ -12,7 +12,7 @@ * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2015-2016 ForgeRock AS. - * Portions Copyright 2020-2023 Wren Security + * Portions Copyright 2020-2024 Wren Security */ package org.forgerock.openidm.maintenance.upgrade; @@ -53,8 +53,6 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.jar.Attributes; import java.util.regex.Pattern; -import java.util.zip.ZipException; -import java.util.zip.ZipFile; import org.apache.commons.io.FileUtils; import org.forgerock.commons.launcher.OSGiFrameworkService; import org.forgerock.json.JsonPointer; @@ -124,7 +122,7 @@ public class UpdateManagerImpl implements UpdateManager { private static final String UPDATE_CONFIG_FILE = "update.json"; private static final Path CHECKSUMS_FILE = Paths.get(".checksums.csv"); - private static final Path CHECKSUMS_FILE_IN_OPENIDM = Paths.get("openidm/.checksums.csv"); + private static final String CHECKSUMS_FILE_IN_OPENIDM = "openidm/.checksums.csv"; private static final Path BUNDLE_PATH = Paths.get("bundle"); private static final Path CONF_PATH = Paths.get("conf"); private static final String JSON_EXT = ".json"; @@ -1265,9 +1263,9 @@ private void createConfig(final Context context, final Path configFile, final Js // XXX undo the work by parsePid to make sure we call create on config properly final String[] paths = pid.split("/"); final CreateRequest request; - if (paths.length == 2) + if (paths.length == 2) { request = Requests.newCreateRequest("config/" + paths[0], paths[1], content); - else { + } else { request = Requests.newCreateRequest("config", paths[0], content); } @@ -1313,9 +1311,9 @@ private void deleteConfig(final Context context, final Path configFile) throws R // XXX undo the work by parsePid to make sure we call delete on config properly final String[] paths = pid.split("/"); final DeleteRequest request; - if (paths.length == 2) + if (paths.length == 2) { request = Requests.newDeleteRequest("config/" + paths[0], paths[1]); - else { + } else { request = Requests.newDeleteRequest("config", paths[0]); } @@ -1364,9 +1362,9 @@ private String getDateString(Date date) { * @return the directory that holds the file to be extracted * @throws UpdateException */ - Path extractFileToDirectory(File zipFile, Path fileToExtract) throws UpdateException { + Path extractFileToDirectory(File zipFile, String fileToExtract) throws UpdateException { try { - PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + fileToExtract.toString()); + PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + fileToExtract); Path tmpDir = Files.createTempDirectory(UUID.randomUUID().toString()); ZipUtils.unzipFile(zipFile.toPath(), pathMatcher, tmpDir); return tmpDir.resolve(fileToExtract).getParent(); @@ -1385,7 +1383,7 @@ JsonValue readUpdateConfig(File file) throws InvalidArchiveUpdateException { final Path tmpDir; try { - tmpDir = extractFileToDirectory(file, Paths.get("openidm/" + UPDATE_CONFIG_FILE)); + tmpDir = extractFileToDirectory(file, "openidm/" + UPDATE_CONFIG_FILE); } catch (UpdateException e) { throw new InvalidArchiveUpdateException(file.toString(), "Unable to load " + UPDATE_CONFIG_FILE + ".", e); } diff --git a/openidm-maintenance/src/test/java/org/forgerock/openidm/maintenance/upgrade/UpdateManagerImplTest.java b/openidm-maintenance/src/test/java/org/forgerock/openidm/maintenance/upgrade/UpdateManagerImplTest.java index be2f9233b0..472bd7143f 100644 --- a/openidm-maintenance/src/test/java/org/forgerock/openidm/maintenance/upgrade/UpdateManagerImplTest.java +++ b/openidm-maintenance/src/test/java/org/forgerock/openidm/maintenance/upgrade/UpdateManagerImplTest.java @@ -12,7 +12,7 @@ * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2016 ForgeRock AS. - * Portions Copyright 2018 Wren Security. + * Portions Copyright 2018-2024 Wren Security. */ package org.forgerock.openidm.maintenance.upgrade; @@ -35,8 +35,8 @@ import org.assertj.core.api.Assertions; import org.forgerock.json.JsonValue; import org.forgerock.openidm.core.IdentityServer; -import org.forgerock.openidm.core.ServerConstants; import org.forgerock.openidm.core.IdentityServerTestUtils; +import org.forgerock.openidm.core.ServerConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.BeforeClass; @@ -79,19 +79,24 @@ private UpdateManagerImpl newUpdateManager() { { bindUpdateLogService(mock(UpdateLogService.class)); } + @Override File[] getUpdateFiles() { return new File[]{ archiveFile }; } + @Override JsonValue readUpdateConfig(File file) throws InvalidArchiveUpdateException { return testConfig; } + @Override ChecksumFile resolveChecksumFile(Path path) throws UpdateException { return mock(ChecksumFile.class); } - Path extractFileToDirectory(File zipFile, Path fileToExtract) throws UpdateException { + @Override + Path extractFileToDirectory(File zipFile, String fileToExtract) throws UpdateException { return mock(Path.class); } // This is to avoid ServiceTracker issues in test + @Override String getDbDirName() { return "mysql"; } @@ -147,12 +152,15 @@ public void testListAvailableUpdatesBadProperties() throws Exception { // this UpdateManagerImpl throws an exception on reading the update config to test // listAvailableUpdates' ability to return the proper error UpdateManagerImpl updateManager = new UpdateManagerImpl() { + @Override File[] getUpdateFiles() { return new File[]{ archiveFile }; } + @Override JsonValue readUpdateConfig(File file) throws InvalidArchiveUpdateException { throw new InvalidArchiveUpdateException("test.zip", "bad properties"); } + @Override ChecksumFile resolveChecksumFile(Path path) throws UpdateException { return mock(ChecksumFile.class); } @@ -239,16 +247,20 @@ public void testListAvailableUpdatesMissingChecksum() throws Exception { // to simulate a bad checksum in order to test listAvailableUpdates' // ability to return the proper error UpdateManagerImpl updateManager = new UpdateManagerImpl() { + @Override File[] getUpdateFiles() { return new File[]{ archiveFile }; } + @Override JsonValue readUpdateConfig(File file) throws InvalidArchiveUpdateException { return testConfig; } + @Override ChecksumFile resolveChecksumFile(Path path) throws UpdateException { return mock(ChecksumFile.class); } - Path extractFileToDirectory(File zipFile, Path fileToExtract) throws UpdateException { + @Override + Path extractFileToDirectory(File zipFile, String fileToExtract) throws UpdateException { throw new UpdateException("missing checksum file"); } }; @@ -281,6 +293,7 @@ public Object[][] versions() { @Test(dataProvider = "versions") public void testGetBaseProductVersion(final String fullVersion, final String baseVersion, boolean unused) { UpdateManagerImpl updateManager = new UpdateManagerImpl() { + @Override String getProductVersion() { return fullVersion; } @@ -305,9 +318,11 @@ public void testArchiveVersionMatch(final String version, final String baseVersi field("restartRequired", false) )); UpdateManagerImpl updateManager = new UpdateManagerImpl() { + @Override String getProductVersion() { return version; } + @Override String getBaseProductVersion() { return baseVersion; } diff --git a/openidm-maintenance/src/test/java/org/forgerock/openidm/maintenance/upgrade/ZipUtilsTest.java b/openidm-maintenance/src/test/java/org/forgerock/openidm/maintenance/upgrade/ZipUtilsTest.java index 9e0ee4bb9d..bae2cc097d 100644 --- a/openidm-maintenance/src/test/java/org/forgerock/openidm/maintenance/upgrade/ZipUtilsTest.java +++ b/openidm-maintenance/src/test/java/org/forgerock/openidm/maintenance/upgrade/ZipUtilsTest.java @@ -11,14 +11,16 @@ * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions copyright [year] [name of copyright owner]". * - * Copyright 2023 Wren Security. All rights reserved. + * Copyright 2023-2024 Wren Security. All rights reserved. */ package org.forgerock.openidm.maintenance.upgrade; +import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import java.io.BufferedOutputStream; +import java.io.File; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.FileVisitResult; @@ -27,16 +29,18 @@ import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; +import java.util.Comparator; +import java.util.function.Predicate; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -import org.codehaus.plexus.util.FileUtils; -import org.junit.Test; +import org.testng.annotations.Test; public class ZipUtilsTest { @Test public void testUnzipFile() throws IOException { Path workDir = Files.createTempDirectory("zipUtilsTest"); + // Prepare test files Path sourceDir = Files.createDirectory(workDir.resolve("source")); Path rootFilename = Paths.get("root.txt"); @@ -47,19 +51,31 @@ public void testUnzipFile() throws IOException { Path zipFile = workDir.resolve("test.zip"); createZipArchive(zipFile, sourceDir); Path targetDir = Files.createDirectory(workDir.resolve("target")); + // Check extraction of all files ZipUtils.unzipFile(zipFile, targetDir); assertTrue(Files.exists(targetDir.resolve(rootFilename))); assertTrue(Files.exists(targetDir.resolve(nestedFilename))); - FileUtils.contentEquals(sourceDir.resolve(rootFilename).toFile(), targetDir.resolve(rootFilename).toFile()); - FileUtils.contentEquals(sourceDir.resolve(nestedFilename).toFile(), targetDir.resolve(nestedFilename).toFile()); - // Check extraction of specific file - FileUtils.cleanDirectory(targetDir.toString()); - ZipUtils.unzipFile(zipFile, FileSystems.getDefault().getPathMatcher("glob:" + nestedFilename.toString()), targetDir); + assertEquals(Files.readString(targetDir.resolve(rootFilename)), + Files.readString(sourceDir.resolve(rootFilename))); + assertEquals(Files.readString(targetDir.resolve(nestedFilename)), + Files.readString(sourceDir.resolve(nestedFilename))); + + // Check extraction of a specific file + cleanDirectory(targetDir); + ZipUtils.unzipFile(zipFile, FileSystems.getDefault().getPathMatcher("glob:folder/nested.txt"), targetDir); assertFalse(Files.exists(targetDir.resolve(rootFilename))); assertTrue(Files.exists(targetDir.resolve(nestedFilename))); - FileUtils.contentEquals(sourceDir.resolve(nestedFilename).toFile(), targetDir.resolve(nestedFilename).toFile()); + assertEquals(Files.readString(targetDir.resolve(nestedFilename)), + Files.readString(sourceDir.resolve(nestedFilename))); + } + private void cleanDirectory(Path path) throws IOException { + Files.walk(path) + .sorted(Comparator.reverseOrder()) + .filter(Predicate.not(path::equals)) + .map(Path::toFile) + .forEach(File::delete); } private void createZipArchive(Path targetFile, Path sourceFolder) { @@ -80,5 +96,4 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO } } - }