Skip to content

Commit

Permalink
Simplify and tidy up config (#74)
Browse files Browse the repository at this point in the history
Tidy up model. Relocation: drop all that mumbo-jumbo. In general simplified SecUtil.
  • Loading branch information
cstamas authored Sep 29, 2024
1 parent b080ef6 commit 252c990
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand All @@ -26,7 +29,7 @@ public interface SecDispatcher {
* The default path of configuration.
* <p>
* 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";

Expand All @@ -53,7 +56,7 @@ public interface SecDispatcher {
Set<String> availableCiphers();

/**
* encrypt given plaintext string
* Encrypt given plaintext string.
*
* @param str the plaintext to encrypt
* @param attr the attributes, may be {@code null}
Expand All @@ -63,11 +66,28 @@ public interface SecDispatcher {
String encrypt(String str, Map<String, String> 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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
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;
import java.util.Set;
Expand Down Expand Up @@ -115,6 +118,21 @@ public String decrypt(String str) throws SecDispatcherException {
}
}

@Override
public SettingsSecurity readConfiguration(boolean createIfMissing) throws IOException {
SettingsSecurity configuration = SecUtil.read(getConfigurationPath());
if (configuration == null && createIfMissing) {
configuration = new SettingsSecurity();
}
return configuration;
}

@Override
public void writeConfiguration(SettingsSecurity configuration) throws IOException {
requireNonNull(configuration, "configuration is null");
SecUtil.write(getConfigurationPath(), configuration, true);
}

private Map<String, String> prepareDispatcherConfig(String type) {
HashMap<String, String> dispatcherConf = new HashMap<>();
SettingsSecurity sec = getConfiguration(false);
Expand Down Expand Up @@ -167,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(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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,23 @@

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Paths;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;

import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
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;

Expand All @@ -40,44 +44,25 @@
* @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"};

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) throws IOException {
requireNonNull(configurationFile, "configurationFile must not be null");
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);
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);
}
}

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();
}
}
throw new IOException("Parsing error", e);
}
return Files.newInputStream(Paths.get(resource));
}

public static Map<String, String> getConfig(SettingsSecurity sec, String name) {
Expand All @@ -102,4 +87,41 @@ public static Map<String, String> getConfig(SettingsSecurity sec, String name) {
}
return null;
}

private static final boolean IS_WINDOWS =
System.getProperty("os.name", "unknown").startsWith("Windows");

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() + "."
+ Long.toUnsignedString(ThreadLocalRandom.current().nextLong()) + ".tmp");

configuration.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion());
configuration.setModelEncoding(StandardCharsets.UTF_8.name());

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) {
try (InputStream is = Files.newInputStream(tempFile);
OutputStream os = Files.newOutputStream(target)) {
is.transferTo(os);
}
} else {
Files.move(tempFile, target, StandardCopyOption.REPLACE_EXISTING);
}
} catch (XMLStreamException e) {
throw new IOException("XML Processing error", e);
} finally {
Files.deleteIfExists(tempFile);
}
}
}
46 changes: 14 additions & 32 deletions src/main/mdo/settings-security.mdo
Original file line number Diff line number Diff line change
Expand Up @@ -19,91 +19,80 @@
xml.schemaLocation="https://codehaus-plexus.github.io/xsd/plexus-sec-dispatcher-${version}.xsd">

<id>settings-security</id>

<name>SecurityConfiguration</name>
<description>SecurityConfiguration</description>

<defaults>
<default>
<key>package</key>
<value>org.codehaus.plexus.components.secdispatcher.model</value>
</default>
</defaults>

<classes>

<classes>
<class rootElement="true">
<name>SettingsSecurity</name>
<version>1.0.0+</version>
<fields>

<field>
<name>master</name>
<version>1.0.0/2.1.0</version>
<type>String</type>
<description>encrypted master password</description>
</field>

<field>
<name>relocation</name>
<version>1.0.0/2.1.0</version>
<type>String</type>
<required>false</required>
<description>Relocates configuration to given reference. Reference if relative, will be resolved from the relocated configuration directory</description>
</field>
<field>
<name>modelVersion</name>
<version>3.0.0+</version>
<type>String</type>
<required>true</required>
<description>The version of the model</description>
</field>

<field>
<name>masterSource</name>
<version>3.0.0+</version>
<type>String</type>
<required>true</required>
<description>The URI describing the source of the master password</description>
<description>The masterSource describes the source of the master password</description>
</field>

<field>
<name>masterCipher</name>
<version>3.0.0+</version>
<type>String</type>
<required>true</required>
<description>The Cipher to be used</description>
<description>The Cipher to be used for master password</description>
</field>

<field>
<name>relocation</name>
<version>1.0.0+</version>
<type>String</type>
<required>false</required>
<description>reference to the location of the security file</description>
</field>

<field>
<name>configurations</name>
<version>1.0.0+</version>
<description>named configurations</description>
<description>Optional named Dispatcher configurations</description>
<required>false</required>
<association>
<type>Config</type>
<multiplicity>*</multiplicity>
</association>
</field>

</fields>
</class>

<class>
<name>Config</name>
<version>1.0.0+</version>
<description>Named configuration</description>
<description>Named Dispatcher configuration</description>
<fields>

<field>
<name>name</name>
<type>String</type>
<required>true</required>
<version>1.0.0+</version>
<description>name of this configuration</description>
<description>Name of Dispatcher configuration is meant for</description>
</field>

<field>
<name>properties</name>
<version>1.0.0+</version>
Expand All @@ -113,35 +102,28 @@
<multiplicity>*</multiplicity>
</association>
</field>

</fields>
</class>

<class>
<name>ConfigProperty</name>
<version>1.0.0+</version>
<description>generic property - name/value pair</description>

<fields>

<field>
<name>name</name>
<type>String</type>
<required>true</required>
<version>1.0.0+</version>
<description>name of this property</description>
</field>

<field>
<name>value</name>
<type>String</type>
<required>true</required>
<version>1.0.0+</version>
<description>value of this property</description>
</field>

</fields>
</class>

</classes>
</model>
Loading

0 comments on commit 252c990

Please sign in to comment.