From 63d9dd3b25d51e945ed29fafab4dcfc8b8ba0ea6 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sat, 28 Sep 2024 23:03:10 +0200 Subject: [PATCH 1/6] Simplify and tidy up config Tidy up model. Relocation: drop all that webdav mumbo-jumbo, and for now relocation is interpreted as Path, that if relative, is resolved from the relocated configuration directory. --- .../secdispatcher/SecDispatcher.java | 2 +- .../internal/DefaultSecDispatcher.java | 3 +- .../secdispatcher/internal/SecUtil.java | 52 +++++++++---------- src/main/mdo/settings-security.mdo | 34 +++--------- .../secdispatcher/internal/SecUtilTest.java | 44 ++++++++++++++-- 5 files changed, 78 insertions(+), 57 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java index 53c64a8..b613619 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java @@ -26,7 +26,7 @@ public interface SecDispatcher { * The default path of configuration. *

* The character {@code ~} (tilde) may be present as first character ONLY and is - * interpreted as "user home". + * interpreted as "user.home" system property, and it MUST be followed by path separator. */ String DEFAULT_CONFIGURATION = "~/.m2/settings-security.xml"; diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java index 38da707..d143c09 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java @@ -17,6 +17,7 @@ import javax.inject.Named; import javax.inject.Singleton; +import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -170,7 +171,7 @@ private boolean isEncryptedString(String str) { private SettingsSecurity getConfiguration(boolean mandatory) throws SecDispatcherException { String location = System.getProperty(SYSTEM_PROPERTY_CONFIGURATION_LOCATION, getConfigurationFile()); location = location.charAt(0) == '~' ? System.getProperty("user.home") + location.substring(1) : location; - SettingsSecurity sec = SecUtil.read(location, true); + SettingsSecurity sec = SecUtil.read(Paths.get(location), true); if (mandatory && sec == null) throw new SecDispatcherException("Please check that configuration file on path " + location + " exists"); diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java index ebf400e..7dc2f5e 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java @@ -17,11 +17,11 @@ import java.io.IOException; import java.io.InputStream; -import java.net.URL; import java.nio.file.Files; import java.nio.file.NoSuchFileException; -import java.nio.file.Paths; +import java.nio.file.Path; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -40,21 +40,36 @@ * @version $Id$ * */ -public class SecUtil { +public final class SecUtil { + private SecUtil() {} - public static final String PROTOCOL_DELIM = "://"; - public static final int PROTOCOL_DELIM_LEN = PROTOCOL_DELIM.length(); - public static final String[] URL_PROTOCOLS = - new String[] {"http", "https", "dav", "file", "davs", "webdav", "webdavs", "dav+http", "dav+https"}; + private static final int MAX_RELOCATIONS = 5; - public static SettingsSecurity read(String location, boolean cycle) throws SecDispatcherException { - if (location == null) throw new SecDispatcherException("location to read from is null"); + /** + * Reads the configuration model up, optionally resolving relocation too. + */ + public static SettingsSecurity read(Path configurationFile, boolean followRelocation) + throws SecDispatcherException { + requireNonNull(configurationFile, "configurationFile must not be null"); + LinkedHashSet paths = new LinkedHashSet<>(); + return read(paths, configurationFile, followRelocation); + } + + private static SettingsSecurity read(LinkedHashSet paths, Path configurationFile, boolean follow) + throws SecDispatcherException { + if (!paths.add(configurationFile)) { + throw new SecDispatcherException("Configuration relocation form a cycle: " + paths); + } + if (paths.size() > MAX_RELOCATIONS) { + throw new SecDispatcherException("Configuration relocation is too deep: " + paths); + } SettingsSecurity sec; try { - try (InputStream in = toStream(location)) { + try (InputStream in = Files.newInputStream(configurationFile)) { sec = new SecurityConfigurationStaxReader().read(in); } - if (cycle && sec.getRelocation() != null) return read(sec.getRelocation(), true); + if (follow && sec.getRelocation() != null) + return read(paths, configurationFile.getParent().resolve(sec.getRelocation()), true); return sec; } catch (NoSuchFileException e) { return null; @@ -65,21 +80,6 @@ public static SettingsSecurity read(String location, boolean cycle) throws SecDi } } - private static InputStream toStream(String resource) throws IOException { - requireNonNull(resource, "resource is null"); - int ind = resource.indexOf(PROTOCOL_DELIM); - if (ind > 1) { - String protocol = resource.substring(0, ind); - resource = resource.substring(ind + PROTOCOL_DELIM_LEN); - for (String p : URL_PROTOCOLS) { - if (protocol.regionMatches(true, 0, p, 0, p.length())) { - return new URL(p + PROTOCOL_DELIM + resource).openStream(); - } - } - } - return Files.newInputStream(Paths.get(resource)); - } - public static Map getConfig(SettingsSecurity sec, String name) { if (sec != null && name != null) { List cl = sec.getConfigurations(); diff --git a/src/main/mdo/settings-security.mdo b/src/main/mdo/settings-security.mdo index d671f57..2ea45f7 100644 --- a/src/main/mdo/settings-security.mdo +++ b/src/main/mdo/settings-security.mdo @@ -19,31 +19,27 @@ xml.schemaLocation="https://codehaus-plexus.github.io/xsd/plexus-sec-dispatcher-${version}.xsd"> settings-security - SecurityConfiguration SecurityConfiguration - + package org.codehaus.plexus.components.secdispatcher.model - - + SettingsSecurity 1.0.0+ - master 1.0.0/2.1.0 String encrypted master password - modelVersion 3.0.0+ @@ -51,59 +47,52 @@ true The version of the model - masterSource 3.0.0+ String true - The URI describing the source of the master password + The masterSource describes the source of the master password - masterCipher 3.0.0+ String true - The Cipher to be used + The Cipher to be used for master password - relocation 1.0.0+ String false - reference to the location of the security file + Relocates configuration to given reference. Reference if relative, will be resolved from the relocated configuration directory - configurations 1.0.0+ - named configurations + Optional named Dispatcher configurations false Config * - Config 1.0.0+ - Named configuration + Named Dispatcher configuration - name String true 1.0.0+ - name of this configuration + Name of Dispatcher configuration is meant for - properties 1.0.0+ @@ -113,17 +102,13 @@ * - - ConfigProperty 1.0.0+ generic property - name/value pair - - name String @@ -131,7 +116,6 @@ 1.0.0+ name of this property - value String @@ -139,9 +123,7 @@ 1.0.0+ value of this property - - diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java index 8afc4f1..22462bf 100644 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java @@ -15,9 +15,11 @@ import java.io.OutputStream; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.Map; +import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; import org.codehaus.plexus.components.secdispatcher.model.Config; import org.codehaus.plexus.components.secdispatcher.model.ConfigProperty; import org.codehaus.plexus.components.secdispatcher.model.SettingsSecurity; @@ -27,6 +29,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @@ -41,6 +45,10 @@ public class SecUtilTest { String _propVal = "pval"; private void saveSec(String masterSource) throws Exception { + saveSec("./target/sec1.xml", masterSource); + } + + private void saveSec(String path, String masterSource) throws Exception { SettingsSecurity sec = new SettingsSecurity(); sec.setRelocation(null); @@ -56,7 +64,7 @@ private void saveSec(String masterSource) throws Exception { sec.addConfiguration(conf); - try (OutputStream fos = Files.newOutputStream(Paths.get("./target/sec1.xml"))) { + try (OutputStream fos = Files.newOutputStream(Paths.get(path))) { new SecurityConfigurationStaxWriter().write(fos, sec); } } @@ -65,7 +73,7 @@ private void saveSec(String masterSource) throws Exception { public void prepare() throws Exception { System.setProperty(DefaultSecDispatcher.SYSTEM_PROPERTY_CONFIGURATION_LOCATION, "./target/sec.xml"); SettingsSecurity sec = new SettingsSecurity(); - sec.setRelocation("./target/sec1.xml"); + sec.setRelocation("sec1.xml"); try (OutputStream fos = Files.newOutputStream(Paths.get("./target/sec.xml"))) { new SecurityConfigurationStaxWriter().write(fos, sec); } @@ -74,7 +82,7 @@ public void prepare() throws Exception { @Test void testReadWithRelocation() throws Exception { - SettingsSecurity sec = SecUtil.read("./target/sec.xml", true); + SettingsSecurity sec = SecUtil.read(Paths.get("./target/sec.xml"), true); assertNotNull(sec); assertEquals("magic:mighty", sec.getMasterSource()); Map conf = SecUtil.getConfig(sec, _confName); @@ -82,4 +90,34 @@ void testReadWithRelocation() throws Exception { assertNotNull(conf.get(_propName)); assertEquals(_propVal, conf.get(_propName)); } + + @Test + void testReadWithRelocationCycleSelf() throws Exception { + Path sec1 = Paths.get("./target/sec-cycle-1.xml"); + SettingsSecurity s1 = new SettingsSecurity(); + s1.setRelocation("sec-cycle-1.xml"); + try (OutputStream fos = Files.newOutputStream(sec1)) { + new SecurityConfigurationStaxWriter().write(fos, s1); + } + SecDispatcherException ex = assertThrows(SecDispatcherException.class, () -> SecUtil.read(sec1, true)); + assertTrue(ex.getMessage().contains("cycle")); + } + + @Test + void testReadWithRelocationCycle() throws Exception { + Path sec1 = Paths.get("./target/sec-cycle-1.xml"); + Path sec2 = Paths.get("./target/sec-cycle-2.xml"); + SettingsSecurity s1 = new SettingsSecurity(); + s1.setRelocation("sec-cycle-2.xml"); + try (OutputStream fos = Files.newOutputStream(sec1)) { + new SecurityConfigurationStaxWriter().write(fos, s1); + } + SettingsSecurity s2 = new SettingsSecurity(); + s2.setRelocation("sec-cycle-1.xml"); + try (OutputStream fos = Files.newOutputStream(sec1)) { + new SecurityConfigurationStaxWriter().write(fos, s2); + } + SecDispatcherException ex = assertThrows(SecDispatcherException.class, () -> SecUtil.read(sec1, true)); + assertTrue(ex.getMessage().contains("cycle")); + } } From dd239d20e31f3980c61981e90e998699b13d253c Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sat, 28 Sep 2024 23:12:42 +0200 Subject: [PATCH 2/6] Proper handling --- .../secdispatcher/internal/SecUtilTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java index 22462bf..2196ba9 100644 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java @@ -14,11 +14,13 @@ package org.codehaus.plexus.components.secdispatcher.internal; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Map; +import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; import org.codehaus.plexus.components.secdispatcher.model.Config; import org.codehaus.plexus.components.secdispatcher.model.ConfigProperty; @@ -51,6 +53,8 @@ private void saveSec(String masterSource) throws Exception { private void saveSec(String path, String masterSource) throws Exception { SettingsSecurity sec = new SettingsSecurity(); + sec.setModelEncoding(StandardCharsets.UTF_8.name()); + sec.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion()); sec.setRelocation(null); sec.setMasterSource(masterSource); @@ -73,6 +77,8 @@ private void saveSec(String path, String masterSource) throws Exception { public void prepare() throws Exception { System.setProperty(DefaultSecDispatcher.SYSTEM_PROPERTY_CONFIGURATION_LOCATION, "./target/sec.xml"); SettingsSecurity sec = new SettingsSecurity(); + sec.setModelEncoding(StandardCharsets.UTF_8.name()); + sec.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion()); sec.setRelocation("sec1.xml"); try (OutputStream fos = Files.newOutputStream(Paths.get("./target/sec.xml"))) { new SecurityConfigurationStaxWriter().write(fos, sec); @@ -95,6 +101,8 @@ void testReadWithRelocation() throws Exception { void testReadWithRelocationCycleSelf() throws Exception { Path sec1 = Paths.get("./target/sec-cycle-1.xml"); SettingsSecurity s1 = new SettingsSecurity(); + s1.setModelEncoding(StandardCharsets.UTF_8.name()); + s1.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion()); s1.setRelocation("sec-cycle-1.xml"); try (OutputStream fos = Files.newOutputStream(sec1)) { new SecurityConfigurationStaxWriter().write(fos, s1); @@ -108,11 +116,15 @@ void testReadWithRelocationCycle() throws Exception { Path sec1 = Paths.get("./target/sec-cycle-1.xml"); Path sec2 = Paths.get("./target/sec-cycle-2.xml"); SettingsSecurity s1 = new SettingsSecurity(); + s1.setModelEncoding(StandardCharsets.UTF_8.name()); + s1.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion()); s1.setRelocation("sec-cycle-2.xml"); try (OutputStream fos = Files.newOutputStream(sec1)) { new SecurityConfigurationStaxWriter().write(fos, s1); } SettingsSecurity s2 = new SettingsSecurity(); + s2.setModelEncoding(StandardCharsets.UTF_8.name()); + s2.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion()); s2.setRelocation("sec-cycle-1.xml"); try (OutputStream fos = Files.newOutputStream(sec1)) { new SecurityConfigurationStaxWriter().write(fos, s2); From 01ccc2aa415b9e42a0fb91e03a9d1984637daf06 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sat, 28 Sep 2024 23:25:50 +0200 Subject: [PATCH 3/6] Add write --- .../secdispatcher/internal/SecUtil.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java index 7dc2f5e..159bae2 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java @@ -17,19 +17,26 @@ import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; +import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; import org.codehaus.plexus.components.secdispatcher.model.Config; import org.codehaus.plexus.components.secdispatcher.model.ConfigProperty; import org.codehaus.plexus.components.secdispatcher.model.SettingsSecurity; import org.codehaus.plexus.components.secdispatcher.model.io.stax.SecurityConfigurationStaxReader; +import org.codehaus.plexus.components.secdispatcher.model.io.stax.SecurityConfigurationStaxWriter; import static java.util.Objects.requireNonNull; @@ -102,4 +109,64 @@ public static Map getConfig(SettingsSecurity sec, String name) { } return null; } + + public static void write(Path target, SettingsSecurity configuration) throws IOException { + requireNonNull(target, "file must not be null"); + requireNonNull(configuration, "sec must not be null"); + configuration.setModelVersion(SecDispatcher.class.getPackage().getImplementationVersion()); + configuration.setModelEncoding(StandardCharsets.UTF_8.name()); + writeFile(target, configuration, false); + } + + public static void writeWithBackup(Path target, SettingsSecurity configuration) throws IOException { + requireNonNull(target, "file must not be null"); + requireNonNull(configuration, "sec must not be null"); + configuration.setModelVersion(SecDispatcher.class.getPackage().getImplementationVersion()); + configuration.setModelEncoding(StandardCharsets.UTF_8.name()); + writeFile(target, configuration, true); + } + + private static final boolean IS_WINDOWS = + System.getProperty("os.name", "unknown").startsWith("Windows"); + + private static void writeFile(Path target, SettingsSecurity configuration, boolean doBackup) throws IOException { + requireNonNull(target, "target is null"); + Path parent = requireNonNull(target.getParent(), "target must have parent"); + Files.createDirectories(parent); + Path tempFile = parent.resolve(target.getFileName() + "." + + Long.toUnsignedString(ThreadLocalRandom.current().nextLong()) + ".tmp"); + try (OutputStream out = Files.newOutputStream(tempFile)) { + new SecurityConfigurationStaxWriter().write(out, configuration); + if (doBackup && Files.isRegularFile(target)) { + Files.copy(target, parent.resolve(target.getFileName() + ".bak"), StandardCopyOption.REPLACE_EXISTING); + } + if (IS_WINDOWS) { + copy(tempFile, target); + } else { + Files.move(tempFile, target, StandardCopyOption.REPLACE_EXISTING); + } + } catch (XMLStreamException e) { + throw new IOException("XML Processing error", e); + } finally { + Files.deleteIfExists(tempFile); + } + } + + /** + * On Windows we use pre-NIO2 way to copy files, as for some reason it works. Beat me why. + */ + private static void copy(Path source, Path target) throws IOException { + ByteBuffer buffer = ByteBuffer.allocate(1024 * 32); + byte[] array = buffer.array(); + try (InputStream is = Files.newInputStream(source); + OutputStream os = Files.newOutputStream(target)) { + while (true) { + int bytes = is.read(array); + if (bytes < 0) { + break; + } + os.write(array, 0, bytes); + } + } + } } From fa948b295db8b3f4ec8fdce295ed5c5c0c045ce5 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sat, 28 Sep 2024 23:51:39 +0200 Subject: [PATCH 4/6] Drop relocation --- .../secdispatcher/SecDispatcher.java | 26 ++++- .../internal/DefaultSecDispatcher.java | 35 ++++++- .../secdispatcher/internal/SecUtil.java | 33 ++----- src/main/mdo/settings-security.mdo | 14 +-- .../secdispatcher/internal/SecUtilTest.java | 94 +++++-------------- 5 files changed, 90 insertions(+), 112 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java index b613619..04ef9dd 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java @@ -13,9 +13,12 @@ package org.codehaus.plexus.components.secdispatcher; +import java.io.IOException; import java.util.Map; import java.util.Set; +import org.codehaus.plexus.components.secdispatcher.model.SettingsSecurity; + /** * This component decrypts a string, passed to it * @@ -53,7 +56,7 @@ public interface SecDispatcher { Set availableCiphers(); /** - * encrypt given plaintext string + * Encrypt given plaintext string. * * @param str the plaintext to encrypt * @param attr the attributes, may be {@code null} @@ -63,11 +66,28 @@ public interface SecDispatcher { String encrypt(String str, Map attr) throws SecDispatcherException; /** - * decrypt given encrypted string + * Decrypt given encrypted string. * * @param str the encrypted string - * @return plaintext string + * @return decrypted string * @throws SecDispatcherException in case of problem */ String decrypt(String str) throws SecDispatcherException; + + /** + * Reads the effective configuration, eventually creating new instance if not present. + * + * @param createIfMissing If {@code true}, it will create a new empty instance + * @return the configuration, of {@code null} if it does not exist in {@code createIfMissing} is {@code false} + * @throws IOException In case of IO problem + */ + SettingsSecurity readConfiguration(boolean createIfMissing) throws IOException; + + /** + * Writes the effective configuration. + * + * @param configuration The configuration to write, may not be {@code null} + * @throws IOException In case of IO problem + */ + void writeConfiguration(SettingsSecurity configuration) throws IOException; } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java index d143c09..e1b356d 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java @@ -17,6 +17,8 @@ import javax.inject.Named; import javax.inject.Singleton; +import java.io.IOException; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; @@ -116,6 +118,21 @@ public String decrypt(String str) throws SecDispatcherException { } } + @Override + public SettingsSecurity readConfiguration(boolean createIfMissing) throws IOException { + SettingsSecurity configuration = getConfiguration(false); + if (configuration == null && createIfMissing) { + configuration = new SettingsSecurity(); + } + return configuration; + } + + @Override + public void writeConfiguration(SettingsSecurity configuration) throws IOException { + requireNonNull(configuration, "configuration is null"); + SecUtil.writeWithBackup(getConfigurationPath(), configuration); + } + private Map prepareDispatcherConfig(String type) { HashMap dispatcherConf = new HashMap<>(); SettingsSecurity sec = getConfiguration(false); @@ -168,14 +185,22 @@ private boolean isEncryptedString(String str) { return cipher.isEncryptedString(str); } - private SettingsSecurity getConfiguration(boolean mandatory) throws SecDispatcherException { + private Path getConfigurationPath() { String location = System.getProperty(SYSTEM_PROPERTY_CONFIGURATION_LOCATION, getConfigurationFile()); location = location.charAt(0) == '~' ? System.getProperty("user.home") + location.substring(1) : location; - SettingsSecurity sec = SecUtil.read(Paths.get(location), true); - if (mandatory && sec == null) - throw new SecDispatcherException("Please check that configuration file on path " + location + " exists"); + return Paths.get(location); + } - return sec; + private SettingsSecurity getConfiguration(boolean mandatory) throws SecDispatcherException { + Path path = getConfigurationPath(); + try { + SettingsSecurity sec = SecUtil.read(path); + if (mandatory && sec == null) + throw new SecDispatcherException("Please check that configuration file on path " + path + " exists"); + return sec; + } catch (IOException e) { + throw new SecDispatcherException(e.getMessage(), e); + } } private String getMasterPassword(SettingsSecurity sec, boolean mandatory) throws SecDispatcherException { diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java index 159bae2..5e77adb 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java @@ -25,13 +25,11 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.HashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.concurrent.ThreadLocalRandom; import org.codehaus.plexus.components.secdispatcher.SecDispatcher; -import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; import org.codehaus.plexus.components.secdispatcher.model.Config; import org.codehaus.plexus.components.secdispatcher.model.ConfigProperty; import org.codehaus.plexus.components.secdispatcher.model.SettingsSecurity; @@ -50,40 +48,21 @@ public final class SecUtil { private SecUtil() {} - private static final int MAX_RELOCATIONS = 5; - /** * Reads the configuration model up, optionally resolving relocation too. */ - public static SettingsSecurity read(Path configurationFile, boolean followRelocation) - throws SecDispatcherException { + public static SettingsSecurity read(Path configurationFile) throws IOException { requireNonNull(configurationFile, "configurationFile must not be null"); - LinkedHashSet paths = new LinkedHashSet<>(); - return read(paths, configurationFile, followRelocation); - } - - private static SettingsSecurity read(LinkedHashSet paths, Path configurationFile, boolean follow) - throws SecDispatcherException { - if (!paths.add(configurationFile)) { - throw new SecDispatcherException("Configuration relocation form a cycle: " + paths); - } - if (paths.size() > MAX_RELOCATIONS) { - throw new SecDispatcherException("Configuration relocation is too deep: " + paths); - } SettingsSecurity sec; try { try (InputStream in = Files.newInputStream(configurationFile)) { sec = new SecurityConfigurationStaxReader().read(in); } - if (follow && sec.getRelocation() != null) - return read(paths, configurationFile.getParent().resolve(sec.getRelocation()), true); return sec; } catch (NoSuchFileException e) { return null; - } catch (IOException e) { - throw new SecDispatcherException("IO Problem", e); } catch (XMLStreamException e) { - throw new SecDispatcherException("Parsing error", e); + throw new IOException("Parsing error", e); } } @@ -113,16 +92,12 @@ public static Map getConfig(SettingsSecurity sec, String name) { public static void write(Path target, SettingsSecurity configuration) throws IOException { requireNonNull(target, "file must not be null"); requireNonNull(configuration, "sec must not be null"); - configuration.setModelVersion(SecDispatcher.class.getPackage().getImplementationVersion()); - configuration.setModelEncoding(StandardCharsets.UTF_8.name()); writeFile(target, configuration, false); } public static void writeWithBackup(Path target, SettingsSecurity configuration) throws IOException { requireNonNull(target, "file must not be null"); requireNonNull(configuration, "sec must not be null"); - configuration.setModelVersion(SecDispatcher.class.getPackage().getImplementationVersion()); - configuration.setModelEncoding(StandardCharsets.UTF_8.name()); writeFile(target, configuration, true); } @@ -135,6 +110,10 @@ private static void writeFile(Path target, SettingsSecurity configuration, boole Files.createDirectories(parent); Path tempFile = parent.resolve(target.getFileName() + "." + Long.toUnsignedString(ThreadLocalRandom.current().nextLong()) + ".tmp"); + + configuration.setModelVersion(SecDispatcher.class.getPackage().getImplementationVersion()); + configuration.setModelEncoding(StandardCharsets.UTF_8.name()); + try (OutputStream out = Files.newOutputStream(tempFile)) { new SecurityConfigurationStaxWriter().write(out, configuration); if (doBackup && Files.isRegularFile(target)) { diff --git a/src/main/mdo/settings-security.mdo b/src/main/mdo/settings-security.mdo index 2ea45f7..336c179 100644 --- a/src/main/mdo/settings-security.mdo +++ b/src/main/mdo/settings-security.mdo @@ -40,6 +40,13 @@ String encrypted master password + + relocation + 1.0.0/2.1.0 + String + false + Relocates configuration to given reference. Reference if relative, will be resolved from the relocated configuration directory + modelVersion 3.0.0+ @@ -61,13 +68,6 @@ true The Cipher to be used for master password - - relocation - 1.0.0+ - String - false - Relocates configuration to given reference. Reference if relative, will be resolved from the relocated configuration directory - configurations 1.0.0+ diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java index 2196ba9..c0972b6 100644 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java @@ -13,25 +13,20 @@ package org.codehaus.plexus.components.secdispatcher.internal; -import java.io.OutputStream; +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.util.Map; -import org.codehaus.plexus.components.secdispatcher.SecDispatcher; -import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; import org.codehaus.plexus.components.secdispatcher.model.Config; import org.codehaus.plexus.components.secdispatcher.model.ConfigProperty; import org.codehaus.plexus.components.secdispatcher.model.SettingsSecurity; -import org.codehaus.plexus.components.secdispatcher.model.io.stax.SecurityConfigurationStaxWriter; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -46,90 +41,49 @@ public class SecUtilTest { String _propName = "pname"; String _propVal = "pval"; - private void saveSec(String masterSource) throws Exception { - saveSec("./target/sec1.xml", masterSource); + private void saveSec(String masterSource) throws IOException { + saveSec("./target/sec.xml", masterSource); } - private void saveSec(String path, String masterSource) throws Exception { + private void saveSec(String path, String masterSource) throws IOException { SettingsSecurity sec = new SettingsSecurity(); - - sec.setModelEncoding(StandardCharsets.UTF_8.name()); - sec.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion()); - sec.setRelocation(null); sec.setMasterSource(masterSource); - ConfigProperty cp = new ConfigProperty(); cp.setName(_propName); cp.setValue(_propVal); - Config conf = new Config(); conf.setName(_confName); conf.addProperty(cp); - sec.addConfiguration(conf); - - try (OutputStream fos = Files.newOutputStream(Paths.get(path))) { - new SecurityConfigurationStaxWriter().write(fos, sec); - } + SecUtil.write(Paths.get(path), sec); } @BeforeEach - public void prepare() throws Exception { - System.setProperty(DefaultSecDispatcher.SYSTEM_PROPERTY_CONFIGURATION_LOCATION, "./target/sec.xml"); - SettingsSecurity sec = new SettingsSecurity(); - sec.setModelEncoding(StandardCharsets.UTF_8.name()); - sec.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion()); - sec.setRelocation("sec1.xml"); - try (OutputStream fos = Files.newOutputStream(Paths.get("./target/sec.xml"))) { - new SecurityConfigurationStaxWriter().write(fos, sec); - } + void prepare() throws IOException { saveSec("magic:mighty"); } @Test - void testReadWithRelocation() throws Exception { - SettingsSecurity sec = SecUtil.read(Paths.get("./target/sec.xml"), true); - assertNotNull(sec); - assertEquals("magic:mighty", sec.getMasterSource()); - Map conf = SecUtil.getConfig(sec, _confName); - assertNotNull(conf); - assertNotNull(conf.get(_propName)); - assertEquals(_propVal, conf.get(_propName)); - } - - @Test - void testReadWithRelocationCycleSelf() throws Exception { - Path sec1 = Paths.get("./target/sec-cycle-1.xml"); - SettingsSecurity s1 = new SettingsSecurity(); - s1.setModelEncoding(StandardCharsets.UTF_8.name()); - s1.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion()); - s1.setRelocation("sec-cycle-1.xml"); - try (OutputStream fos = Files.newOutputStream(sec1)) { - new SecurityConfigurationStaxWriter().write(fos, s1); - } - SecDispatcherException ex = assertThrows(SecDispatcherException.class, () -> SecUtil.read(sec1, true)); - assertTrue(ex.getMessage().contains("cycle")); + void readWrite() throws IOException { + Path path = Path.of("./target/sec.xml"); + SettingsSecurity config = SecUtil.read(path); + assertNotNull(config); + assertEquals(SettingsSecurity.class.getPackage().getSpecificationVersion(), config.getModelVersion()); + assertEquals(StandardCharsets.UTF_8.name(), config.getModelEncoding()); + assertEquals("magic:mighty", config.getMasterSource()); + SecUtil.write(path, config); } @Test - void testReadWithRelocationCycle() throws Exception { - Path sec1 = Paths.get("./target/sec-cycle-1.xml"); - Path sec2 = Paths.get("./target/sec-cycle-2.xml"); - SettingsSecurity s1 = new SettingsSecurity(); - s1.setModelEncoding(StandardCharsets.UTF_8.name()); - s1.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion()); - s1.setRelocation("sec-cycle-2.xml"); - try (OutputStream fos = Files.newOutputStream(sec1)) { - new SecurityConfigurationStaxWriter().write(fos, s1); - } - SettingsSecurity s2 = new SettingsSecurity(); - s2.setModelEncoding(StandardCharsets.UTF_8.name()); - s2.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion()); - s2.setRelocation("sec-cycle-1.xml"); - try (OutputStream fos = Files.newOutputStream(sec1)) { - new SecurityConfigurationStaxWriter().write(fos, s2); - } - SecDispatcherException ex = assertThrows(SecDispatcherException.class, () -> SecUtil.read(sec1, true)); - assertTrue(ex.getMessage().contains("cycle")); + void readWriteWithBackup() throws IOException { + Path path = Path.of("./target/sec.xml"); + SettingsSecurity config = SecUtil.read(path); + assertNotNull(config); + assertEquals(SettingsSecurity.class.getPackage().getSpecificationVersion(), config.getModelVersion()); + assertEquals(StandardCharsets.UTF_8.name(), config.getModelEncoding()); + assertEquals("magic:mighty", config.getMasterSource()); + SecUtil.writeWithBackup(path, config); + assertTrue(Files.exists(path)); + assertTrue(Files.exists(path.getParent().resolve(path.getFileName() + ".bak"))); } } From 41d483d272684e2b5b58d7e0e1d36029b4a9be7f Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sat, 28 Sep 2024 23:57:47 +0200 Subject: [PATCH 5/6] Wrong method --- .../components/secdispatcher/internal/DefaultSecDispatcher.java | 2 +- .../plexus/components/secdispatcher/internal/SecUtil.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java index e1b356d..44f6131 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java @@ -120,7 +120,7 @@ public String decrypt(String str) throws SecDispatcherException { @Override public SettingsSecurity readConfiguration(boolean createIfMissing) throws IOException { - SettingsSecurity configuration = getConfiguration(false); + SettingsSecurity configuration = SecUtil.read(getConfigurationPath()); if (configuration == null && createIfMissing) { configuration = new SettingsSecurity(); } diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java index 5e77adb..89440e6 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java @@ -111,7 +111,7 @@ private static void writeFile(Path target, SettingsSecurity configuration, boole Path tempFile = parent.resolve(target.getFileName() + "." + Long.toUnsignedString(ThreadLocalRandom.current().nextLong()) + ".tmp"); - configuration.setModelVersion(SecDispatcher.class.getPackage().getImplementationVersion()); + configuration.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion()); configuration.setModelEncoding(StandardCharsets.UTF_8.name()); try (OutputStream out = Files.newOutputStream(tempFile)) { From 267af58d21984ba4ad06ad4c465bde091adb56f5 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Sun, 29 Sep 2024 17:12:40 +0200 Subject: [PATCH 6/6] Slim down things, PR comment applied --- .../internal/DefaultSecDispatcher.java | 2 +- .../secdispatcher/internal/SecUtil.java | 48 +++++-------------- .../secdispatcher/internal/SecUtilTest.java | 6 +-- 3 files changed, 16 insertions(+), 40 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java index 44f6131..131c0de 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java @@ -130,7 +130,7 @@ public SettingsSecurity readConfiguration(boolean createIfMissing) throws IOExce @Override public void writeConfiguration(SettingsSecurity configuration) throws IOException { requireNonNull(configuration, "configuration is null"); - SecUtil.writeWithBackup(getConfigurationPath(), configuration); + SecUtil.write(getConfigurationPath(), configuration, true); } private Map prepareDispatcherConfig(String type) { diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java index 89440e6..0338683 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java @@ -18,7 +18,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.NoSuchFileException; @@ -89,23 +88,12 @@ public static Map getConfig(SettingsSecurity sec, String name) { return null; } - public static void write(Path target, SettingsSecurity configuration) throws IOException { - requireNonNull(target, "file must not be null"); - requireNonNull(configuration, "sec must not be null"); - writeFile(target, configuration, false); - } - - public static void writeWithBackup(Path target, SettingsSecurity configuration) throws IOException { - requireNonNull(target, "file must not be null"); - requireNonNull(configuration, "sec must not be null"); - writeFile(target, configuration, true); - } - private static final boolean IS_WINDOWS = System.getProperty("os.name", "unknown").startsWith("Windows"); - private static void writeFile(Path target, SettingsSecurity configuration, boolean doBackup) throws IOException { - requireNonNull(target, "target is null"); + public static void write(Path target, SettingsSecurity configuration, boolean doBackup) throws IOException { + requireNonNull(target, "file must not be null"); + requireNonNull(configuration, "configuration must not be null"); Path parent = requireNonNull(target.getParent(), "target must have parent"); Files.createDirectories(parent); Path tempFile = parent.resolve(target.getFileName() + "." @@ -114,13 +102,19 @@ private static void writeFile(Path target, SettingsSecurity configuration, boole configuration.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion()); configuration.setModelEncoding(StandardCharsets.UTF_8.name()); - try (OutputStream out = Files.newOutputStream(tempFile)) { - new SecurityConfigurationStaxWriter().write(out, configuration); + try { + try (OutputStream tempOut = Files.newOutputStream(tempFile)) { + new SecurityConfigurationStaxWriter().write(tempOut, configuration); + } + if (doBackup && Files.isRegularFile(target)) { Files.copy(target, parent.resolve(target.getFileName() + ".bak"), StandardCopyOption.REPLACE_EXISTING); } if (IS_WINDOWS) { - copy(tempFile, target); + try (InputStream is = Files.newInputStream(tempFile); + OutputStream os = Files.newOutputStream(target)) { + is.transferTo(os); + } } else { Files.move(tempFile, target, StandardCopyOption.REPLACE_EXISTING); } @@ -130,22 +124,4 @@ private static void writeFile(Path target, SettingsSecurity configuration, boole Files.deleteIfExists(tempFile); } } - - /** - * On Windows we use pre-NIO2 way to copy files, as for some reason it works. Beat me why. - */ - private static void copy(Path source, Path target) throws IOException { - ByteBuffer buffer = ByteBuffer.allocate(1024 * 32); - byte[] array = buffer.array(); - try (InputStream is = Files.newInputStream(source); - OutputStream os = Files.newOutputStream(target)) { - while (true) { - int bytes = is.read(array); - if (bytes < 0) { - break; - } - os.write(array, 0, bytes); - } - } - } } diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java index c0972b6..f98dccb 100644 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtilTest.java @@ -55,7 +55,7 @@ private void saveSec(String path, String masterSource) throws IOException { conf.setName(_confName); conf.addProperty(cp); sec.addConfiguration(conf); - SecUtil.write(Paths.get(path), sec); + SecUtil.write(Paths.get(path), sec, false); } @BeforeEach @@ -71,7 +71,7 @@ void readWrite() throws IOException { assertEquals(SettingsSecurity.class.getPackage().getSpecificationVersion(), config.getModelVersion()); assertEquals(StandardCharsets.UTF_8.name(), config.getModelEncoding()); assertEquals("magic:mighty", config.getMasterSource()); - SecUtil.write(path, config); + SecUtil.write(path, config, false); } @Test @@ -82,7 +82,7 @@ void readWriteWithBackup() throws IOException { assertEquals(SettingsSecurity.class.getPackage().getSpecificationVersion(), config.getModelVersion()); assertEquals(StandardCharsets.UTF_8.name(), config.getModelEncoding()); assertEquals("magic:mighty", config.getMasterSource()); - SecUtil.writeWithBackup(path, config); + SecUtil.write(path, config, true); assertTrue(Files.exists(path)); assertTrue(Files.exists(path.getParent().resolve(path.getFileName() + ".bak"))); }