Skip to content

Simplify and tidy up config #74

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Sep 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading