Skip to content

Commit 252c990

Browse files
authored
Simplify and tidy up config (#74)
Tidy up model. Relocation: drop all that mumbo-jumbo. In general simplified SecUtil.
1 parent b080ef6 commit 252c990

File tree

5 files changed

+154
-100
lines changed

5 files changed

+154
-100
lines changed

src/main/java/org/codehaus/plexus/components/secdispatcher/SecDispatcher.java

+24-4
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313

1414
package org.codehaus.plexus.components.secdispatcher;
1515

16+
import java.io.IOException;
1617
import java.util.Map;
1718
import java.util.Set;
1819

20+
import org.codehaus.plexus.components.secdispatcher.model.SettingsSecurity;
21+
1922
/**
2023
* This component decrypts a string, passed to it
2124
*
@@ -26,7 +29,7 @@ public interface SecDispatcher {
2629
* The default path of configuration.
2730
* <p>
2831
* The character {@code ~} (tilde) may be present as first character ONLY and is
29-
* interpreted as "user home".
32+
* interpreted as "user.home" system property, and it MUST be followed by path separator.
3033
*/
3134
String DEFAULT_CONFIGURATION = "~/.m2/settings-security.xml";
3235

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

5558
/**
56-
* encrypt given plaintext string
59+
* Encrypt given plaintext string.
5760
*
5861
* @param str the plaintext to encrypt
5962
* @param attr the attributes, may be {@code null}
@@ -63,11 +66,28 @@ public interface SecDispatcher {
6366
String encrypt(String str, Map<String, String> attr) throws SecDispatcherException;
6467

6568
/**
66-
* decrypt given encrypted string
69+
* Decrypt given encrypted string.
6770
*
6871
* @param str the encrypted string
69-
* @return plaintext string
72+
* @return decrypted string
7073
* @throws SecDispatcherException in case of problem
7174
*/
7275
String decrypt(String str) throws SecDispatcherException;
76+
77+
/**
78+
* Reads the effective configuration, eventually creating new instance if not present.
79+
*
80+
* @param createIfMissing If {@code true}, it will create a new empty instance
81+
* @return the configuration, of {@code null} if it does not exist in {@code createIfMissing} is {@code false}
82+
* @throws IOException In case of IO problem
83+
*/
84+
SettingsSecurity readConfiguration(boolean createIfMissing) throws IOException;
85+
86+
/**
87+
* Writes the effective configuration.
88+
*
89+
* @param configuration The configuration to write, may not be {@code null}
90+
* @throws IOException In case of IO problem
91+
*/
92+
void writeConfiguration(SettingsSecurity configuration) throws IOException;
7393
}

src/main/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcher.java

+31-5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
import javax.inject.Named;
1818
import javax.inject.Singleton;
1919

20+
import java.io.IOException;
21+
import java.nio.file.Path;
22+
import java.nio.file.Paths;
2023
import java.util.HashMap;
2124
import java.util.Map;
2225
import java.util.Set;
@@ -115,6 +118,21 @@ public String decrypt(String str) throws SecDispatcherException {
115118
}
116119
}
117120

121+
@Override
122+
public SettingsSecurity readConfiguration(boolean createIfMissing) throws IOException {
123+
SettingsSecurity configuration = SecUtil.read(getConfigurationPath());
124+
if (configuration == null && createIfMissing) {
125+
configuration = new SettingsSecurity();
126+
}
127+
return configuration;
128+
}
129+
130+
@Override
131+
public void writeConfiguration(SettingsSecurity configuration) throws IOException {
132+
requireNonNull(configuration, "configuration is null");
133+
SecUtil.write(getConfigurationPath(), configuration, true);
134+
}
135+
118136
private Map<String, String> prepareDispatcherConfig(String type) {
119137
HashMap<String, String> dispatcherConf = new HashMap<>();
120138
SettingsSecurity sec = getConfiguration(false);
@@ -167,14 +185,22 @@ private boolean isEncryptedString(String str) {
167185
return cipher.isEncryptedString(str);
168186
}
169187

170-
private SettingsSecurity getConfiguration(boolean mandatory) throws SecDispatcherException {
188+
private Path getConfigurationPath() {
171189
String location = System.getProperty(SYSTEM_PROPERTY_CONFIGURATION_LOCATION, getConfigurationFile());
172190
location = location.charAt(0) == '~' ? System.getProperty("user.home") + location.substring(1) : location;
173-
SettingsSecurity sec = SecUtil.read(location, true);
174-
if (mandatory && sec == null)
175-
throw new SecDispatcherException("Please check that configuration file on path " + location + " exists");
191+
return Paths.get(location);
192+
}
176193

177-
return sec;
194+
private SettingsSecurity getConfiguration(boolean mandatory) throws SecDispatcherException {
195+
Path path = getConfigurationPath();
196+
try {
197+
SettingsSecurity sec = SecUtil.read(path);
198+
if (mandatory && sec == null)
199+
throw new SecDispatcherException("Please check that configuration file on path " + path + " exists");
200+
return sec;
201+
} catch (IOException e) {
202+
throw new SecDispatcherException(e.getMessage(), e);
203+
}
178204
}
179205

180206
private String getMasterPassword(SettingsSecurity sec, boolean mandatory) throws SecDispatcherException {

src/main/java/org/codehaus/plexus/components/secdispatcher/internal/SecUtil.java

+53-31
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,23 @@
1717

1818
import java.io.IOException;
1919
import java.io.InputStream;
20-
import java.net.URL;
20+
import java.io.OutputStream;
21+
import java.nio.charset.StandardCharsets;
2122
import java.nio.file.Files;
2223
import java.nio.file.NoSuchFileException;
23-
import java.nio.file.Paths;
24+
import java.nio.file.Path;
25+
import java.nio.file.StandardCopyOption;
2426
import java.util.HashMap;
2527
import java.util.List;
2628
import java.util.Map;
29+
import java.util.concurrent.ThreadLocalRandom;
2730

28-
import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
31+
import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
2932
import org.codehaus.plexus.components.secdispatcher.model.Config;
3033
import org.codehaus.plexus.components.secdispatcher.model.ConfigProperty;
3134
import org.codehaus.plexus.components.secdispatcher.model.SettingsSecurity;
3235
import org.codehaus.plexus.components.secdispatcher.model.io.stax.SecurityConfigurationStaxReader;
36+
import org.codehaus.plexus.components.secdispatcher.model.io.stax.SecurityConfigurationStaxWriter;
3337

3438
import static java.util.Objects.requireNonNull;
3539

@@ -40,44 +44,25 @@
4044
* @version $Id$
4145
*
4246
*/
43-
public class SecUtil {
47+
public final class SecUtil {
48+
private SecUtil() {}
4449

45-
public static final String PROTOCOL_DELIM = "://";
46-
public static final int PROTOCOL_DELIM_LEN = PROTOCOL_DELIM.length();
47-
public static final String[] URL_PROTOCOLS =
48-
new String[] {"http", "https", "dav", "file", "davs", "webdav", "webdavs", "dav+http", "dav+https"};
49-
50-
public static SettingsSecurity read(String location, boolean cycle) throws SecDispatcherException {
51-
if (location == null) throw new SecDispatcherException("location to read from is null");
50+
/**
51+
* Reads the configuration model up, optionally resolving relocation too.
52+
*/
53+
public static SettingsSecurity read(Path configurationFile) throws IOException {
54+
requireNonNull(configurationFile, "configurationFile must not be null");
5255
SettingsSecurity sec;
5356
try {
54-
try (InputStream in = toStream(location)) {
57+
try (InputStream in = Files.newInputStream(configurationFile)) {
5558
sec = new SecurityConfigurationStaxReader().read(in);
5659
}
57-
if (cycle && sec.getRelocation() != null) return read(sec.getRelocation(), true);
5860
return sec;
5961
} catch (NoSuchFileException e) {
6062
return null;
61-
} catch (IOException e) {
62-
throw new SecDispatcherException("IO Problem", e);
6363
} catch (XMLStreamException e) {
64-
throw new SecDispatcherException("Parsing error", e);
65-
}
66-
}
67-
68-
private static InputStream toStream(String resource) throws IOException {
69-
requireNonNull(resource, "resource is null");
70-
int ind = resource.indexOf(PROTOCOL_DELIM);
71-
if (ind > 1) {
72-
String protocol = resource.substring(0, ind);
73-
resource = resource.substring(ind + PROTOCOL_DELIM_LEN);
74-
for (String p : URL_PROTOCOLS) {
75-
if (protocol.regionMatches(true, 0, p, 0, p.length())) {
76-
return new URL(p + PROTOCOL_DELIM + resource).openStream();
77-
}
78-
}
64+
throw new IOException("Parsing error", e);
7965
}
80-
return Files.newInputStream(Paths.get(resource));
8166
}
8267

8368
public static Map<String, String> getConfig(SettingsSecurity sec, String name) {
@@ -102,4 +87,41 @@ public static Map<String, String> getConfig(SettingsSecurity sec, String name) {
10287
}
10388
return null;
10489
}
90+
91+
private static final boolean IS_WINDOWS =
92+
System.getProperty("os.name", "unknown").startsWith("Windows");
93+
94+
public static void write(Path target, SettingsSecurity configuration, boolean doBackup) throws IOException {
95+
requireNonNull(target, "file must not be null");
96+
requireNonNull(configuration, "configuration must not be null");
97+
Path parent = requireNonNull(target.getParent(), "target must have parent");
98+
Files.createDirectories(parent);
99+
Path tempFile = parent.resolve(target.getFileName() + "."
100+
+ Long.toUnsignedString(ThreadLocalRandom.current().nextLong()) + ".tmp");
101+
102+
configuration.setModelVersion(SecDispatcher.class.getPackage().getSpecificationVersion());
103+
configuration.setModelEncoding(StandardCharsets.UTF_8.name());
104+
105+
try {
106+
try (OutputStream tempOut = Files.newOutputStream(tempFile)) {
107+
new SecurityConfigurationStaxWriter().write(tempOut, configuration);
108+
}
109+
110+
if (doBackup && Files.isRegularFile(target)) {
111+
Files.copy(target, parent.resolve(target.getFileName() + ".bak"), StandardCopyOption.REPLACE_EXISTING);
112+
}
113+
if (IS_WINDOWS) {
114+
try (InputStream is = Files.newInputStream(tempFile);
115+
OutputStream os = Files.newOutputStream(target)) {
116+
is.transferTo(os);
117+
}
118+
} else {
119+
Files.move(tempFile, target, StandardCopyOption.REPLACE_EXISTING);
120+
}
121+
} catch (XMLStreamException e) {
122+
throw new IOException("XML Processing error", e);
123+
} finally {
124+
Files.deleteIfExists(tempFile);
125+
}
126+
}
105127
}

src/main/mdo/settings-security.mdo

+14-32
Original file line numberDiff line numberDiff line change
@@ -19,91 +19,80 @@
1919
xml.schemaLocation="https://codehaus-plexus.github.io/xsd/plexus-sec-dispatcher-${version}.xsd">
2020

2121
<id>settings-security</id>
22-
2322
<name>SecurityConfiguration</name>
2423
<description>SecurityConfiguration</description>
25-
24+
2625
<defaults>
2726
<default>
2827
<key>package</key>
2928
<value>org.codehaus.plexus.components.secdispatcher.model</value>
3029
</default>
3130
</defaults>
32-
33-
<classes>
3431

32+
<classes>
3533
<class rootElement="true">
3634
<name>SettingsSecurity</name>
3735
<version>1.0.0+</version>
3836
<fields>
39-
4037
<field>
4138
<name>master</name>
4239
<version>1.0.0/2.1.0</version>
4340
<type>String</type>
4441
<description>encrypted master password</description>
4542
</field>
46-
43+
<field>
44+
<name>relocation</name>
45+
<version>1.0.0/2.1.0</version>
46+
<type>String</type>
47+
<required>false</required>
48+
<description>Relocates configuration to given reference. Reference if relative, will be resolved from the relocated configuration directory</description>
49+
</field>
4750
<field>
4851
<name>modelVersion</name>
4952
<version>3.0.0+</version>
5053
<type>String</type>
5154
<required>true</required>
5255
<description>The version of the model</description>
5356
</field>
54-
5557
<field>
5658
<name>masterSource</name>
5759
<version>3.0.0+</version>
5860
<type>String</type>
5961
<required>true</required>
60-
<description>The URI describing the source of the master password</description>
62+
<description>The masterSource describes the source of the master password</description>
6163
</field>
62-
6364
<field>
6465
<name>masterCipher</name>
6566
<version>3.0.0+</version>
6667
<type>String</type>
6768
<required>true</required>
68-
<description>The Cipher to be used</description>
69+
<description>The Cipher to be used for master password</description>
6970
</field>
70-
71-
<field>
72-
<name>relocation</name>
73-
<version>1.0.0+</version>
74-
<type>String</type>
75-
<required>false</required>
76-
<description>reference to the location of the security file</description>
77-
</field>
78-
7971
<field>
8072
<name>configurations</name>
8173
<version>1.0.0+</version>
82-
<description>named configurations</description>
74+
<description>Optional named Dispatcher configurations</description>
8375
<required>false</required>
8476
<association>
8577
<type>Config</type>
8678
<multiplicity>*</multiplicity>
8779
</association>
8880
</field>
89-
9081
</fields>
9182
</class>
9283

9384
<class>
9485
<name>Config</name>
9586
<version>1.0.0+</version>
96-
<description>Named configuration</description>
87+
<description>Named Dispatcher configuration</description>
9788
<fields>
98-
9989
<field>
10090
<name>name</name>
10191
<type>String</type>
10292
<required>true</required>
10393
<version>1.0.0+</version>
104-
<description>name of this configuration</description>
94+
<description>Name of Dispatcher configuration is meant for</description>
10595
</field>
106-
10796
<field>
10897
<name>properties</name>
10998
<version>1.0.0+</version>
@@ -113,35 +102,28 @@
113102
<multiplicity>*</multiplicity>
114103
</association>
115104
</field>
116-
117105
</fields>
118106
</class>
119-
120107
<class>
121108
<name>ConfigProperty</name>
122109
<version>1.0.0+</version>
123110
<description>generic property - name/value pair</description>
124-
125111
<fields>
126-
127112
<field>
128113
<name>name</name>
129114
<type>String</type>
130115
<required>true</required>
131116
<version>1.0.0+</version>
132117
<description>name of this property</description>
133118
</field>
134-
135119
<field>
136120
<name>value</name>
137121
<type>String</type>
138122
<required>true</required>
139123
<version>1.0.0+</version>
140124
<description>value of this property</description>
141125
</field>
142-
143126
</fields>
144127
</class>
145-
146128
</classes>
147129
</model>

0 commit comments

Comments
 (0)