Skip to content

Commit 433f8d4

Browse files
committed
Add "deep validation"
1 parent c523461 commit 433f8d4

File tree

13 files changed

+334
-7
lines changed

13 files changed

+334
-7
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,9 @@ EncryptPayload encrypt(String str, Map<String, String> attributes, Map<String, S
6868
*/
6969
String decrypt(String str, Map<String, String> attributes, Map<String, String> config)
7070
throws SecDispatcherException;
71+
72+
/**
73+
* Validates dispatcher configuration.
74+
*/
75+
SecDispatcher.ValidationResponse validateConfiguration(Map<String, String> config);
7176
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* Meta description of dispatcher.
1111
*/
1212
public interface DispatcherMeta {
13-
class Field {
13+
final class Field {
1414
private final String key;
1515
private final boolean optional;
1616
private final String defaultValue;
@@ -67,7 +67,7 @@ public static Builder builder(String key) {
6767
return new Builder(key);
6868
}
6969

70-
public static class Builder {
70+
public static final class Builder {
7171
private final String key;
7272
private boolean optional;
7373
private String defaultValue;

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,13 @@ public interface MasterSource {
3030
* @throws SecDispatcherException If implementation does handle this masterSource, but cannot obtain master password
3131
*/
3232
String handle(String config) throws SecDispatcherException;
33+
34+
/**
35+
* Validates master source configuration.
36+
* <ul>
37+
* <li>if the config cannot be handled by given source, return {@code null}</li>
38+
* <li>otherwise, implementation performs validation and returns non-{@code null} validation response</li>
39+
* </ul>
40+
*/
41+
SecDispatcher.ValidationResponse validateConfiguration(String config);
3342
}

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package org.codehaus.plexus.components.secdispatcher;
1515

1616
import java.io.IOException;
17+
import java.util.List;
1718
import java.util.Map;
1819
import java.util.Set;
1920

@@ -82,4 +83,50 @@ public interface SecDispatcher {
8283
* @throws IOException In case of IO problem
8384
*/
8485
void writeConfiguration(SettingsSecurity configuration) throws IOException;
86+
87+
/**
88+
* The validation response.
89+
*/
90+
final class ValidationResponse {
91+
public enum Level {
92+
INFO,
93+
WARNING,
94+
ERROR
95+
};
96+
97+
private final String source;
98+
private final boolean valid;
99+
private final Map<Level, List<String>> report;
100+
private final List<ValidationResponse> subsystems;
101+
102+
public ValidationResponse(
103+
String source, boolean valid, Map<Level, List<String>> report, List<ValidationResponse> subsystems) {
104+
this.source = source;
105+
this.valid = valid;
106+
this.report = report;
107+
this.subsystems = subsystems;
108+
}
109+
110+
public String getSource() {
111+
return source;
112+
}
113+
114+
public boolean isValid() {
115+
return valid;
116+
}
117+
118+
public Map<Level, List<String>> getReport() {
119+
return report;
120+
}
121+
122+
public List<ValidationResponse> getSubsystems() {
123+
return subsystems;
124+
}
125+
}
126+
127+
/**
128+
* Performs a "deep validation" and reports the status. If return instance {@link ValidationResponse#isValid()}
129+
* is {@code true}, configuration is usable.
130+
*/
131+
ValidationResponse validateConfiguration();
85132
}

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

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.io.IOException;
1717
import java.nio.file.Files;
1818
import java.nio.file.Path;
19+
import java.util.ArrayList;
1920
import java.util.Collection;
2021
import java.util.HashMap;
2122
import java.util.List;
@@ -140,10 +141,12 @@ public String decrypt(String str) throws SecDispatcherException, IOException {
140141
try {
141142
String bare = cipher.unDecorate(str);
142143
Map<String, String> attr = requireNonNull(stripAttributes(bare));
143-
if (attr.get(DISPATCHER_NAME_ATTR) == null) {
144-
// TODO: log?
144+
if (isLegacyPassword(str)) {
145145
attr.put(DISPATCHER_NAME_ATTR, LegacyDispatcher.NAME);
146146
}
147+
if (attr.get(DISPATCHER_NAME_ATTR) == null) {
148+
throw new SecDispatcherException("Invalid encrypted string; mandatory attributes missing");
149+
}
147150
String name = attr.get(DISPATCHER_NAME_ATTR);
148151
Dispatcher dispatcher = dispatchers.get(name);
149152
if (dispatcher == null) throw new SecDispatcherException("no dispatcher for name " + name);
@@ -175,9 +178,55 @@ public void writeConfiguration(SettingsSecurity configuration) throws IOExceptio
175178
SecUtil.write(configurationFile, configuration, true);
176179
}
177180

178-
protected Map<String, String> prepareDispatcherConfig(String type) throws IOException {
181+
@Override
182+
public ValidationResponse validateConfiguration() {
183+
HashMap<ValidationResponse.Level, List<String>> report = new HashMap<>();
184+
ArrayList<ValidationResponse> subsystems = new ArrayList<>();
185+
boolean valid = false;
186+
try {
187+
SettingsSecurity config = readConfiguration(false);
188+
if (config == null) {
189+
report.computeIfAbsent(ValidationResponse.Level.ERROR, k -> new ArrayList<>())
190+
.add("No configuration file found on path " + configurationFile);
191+
} else {
192+
report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>())
193+
.add("Configuration file present on path " + configurationFile);
194+
String defaultDispatcher = config.getDefaultDispatcher();
195+
if (defaultDispatcher == null) {
196+
report.computeIfAbsent(ValidationResponse.Level.ERROR, k -> new ArrayList<>())
197+
.add("No default dispatcher set in configuration");
198+
} else {
199+
report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>())
200+
.add("Default dispatcher set to " + defaultDispatcher);
201+
Dispatcher dispatcher = dispatchers.get(defaultDispatcher);
202+
if (dispatcher == null) {
203+
report.computeIfAbsent(ValidationResponse.Level.ERROR, k -> new ArrayList<>())
204+
.add("Default dispatcher " + defaultDispatcher + " not present in system");
205+
} else {
206+
ValidationResponse dispatcherResponse =
207+
dispatcher.validateConfiguration(prepareDispatcherConfig(defaultDispatcher));
208+
subsystems.add(dispatcherResponse);
209+
if (!dispatcherResponse.isValid()) {
210+
report.computeIfAbsent(ValidationResponse.Level.ERROR, k -> new ArrayList<>())
211+
.add("Default dispatcher " + defaultDispatcher + " configuration is invalid");
212+
} else {
213+
valid = true;
214+
report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>())
215+
.add("Default dispatcher " + defaultDispatcher + " configuration is valid");
216+
}
217+
}
218+
}
219+
}
220+
} catch (IOException e) {
221+
report.computeIfAbsent(ValidationResponse.Level.ERROR, k -> new ArrayList<>())
222+
.add(e.getMessage());
223+
}
224+
return new ValidationResponse(getClass().getSimpleName(), valid, report, subsystems);
225+
}
226+
227+
protected Map<String, String> prepareDispatcherConfig(String name) throws IOException {
179228
HashMap<String, String> dispatcherConf = new HashMap<>();
180-
Map<String, String> conf = SecUtil.getConfig(SecUtil.read(configurationFile), type);
229+
Map<String, String> conf = SecUtil.getConfig(SecUtil.read(configurationFile), name);
181230
if (conf != null) {
182231
dispatcherConf.putAll(conf);
183232
}

src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/LegacyDispatcher.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.codehaus.plexus.components.cipher.PlexusCipherException;
4141
import org.codehaus.plexus.components.secdispatcher.Dispatcher;
4242
import org.codehaus.plexus.components.secdispatcher.DispatcherMeta;
43+
import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
4344
import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
4445
import org.xml.sax.InputSource;
4546

@@ -99,6 +100,17 @@ public String decrypt(String str, Map<String, String> attributes, Map<String, St
99100
}
100101
}
101102

103+
@Override
104+
public SecDispatcher.ValidationResponse validateConfiguration(Map<String, String> config) {
105+
return new SecDispatcher.ValidationResponse(
106+
getClass().getSimpleName(),
107+
false,
108+
Map.of(
109+
SecDispatcher.ValidationResponse.Level.ERROR,
110+
List.of("This dispatcher cannot and must not be directly used via configuration")),
111+
List.of());
112+
}
113+
102114
private String getMasterPassword() throws SecDispatcherException {
103115
String encryptedMasterPassword = getMasterMasterPasswordFromSettingsSecurityXml();
104116
return legacyCipher.decrypt64(plexusCipher.unDecorate(encryptedMasterPassword), MASTER_MASTER_PASSWORD);

src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterDispatcher.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import javax.inject.Named;
1818
import javax.inject.Singleton;
1919

20+
import java.util.ArrayList;
2021
import java.util.Collection;
2122
import java.util.HashMap;
2223
import java.util.List;
@@ -125,6 +126,54 @@ public String decrypt(String str, Map<String, String> attributes, Map<String, St
125126
}
126127
}
127128

129+
@Override
130+
public SecDispatcher.ValidationResponse validateConfiguration(Map<String, String> config) {
131+
HashMap<SecDispatcher.ValidationResponse.Level, List<String>> report = new HashMap<>();
132+
ArrayList<SecDispatcher.ValidationResponse> subsystems = new ArrayList<>();
133+
boolean valid = false;
134+
String masterCipher = config.get(CONF_MASTER_CIPHER);
135+
if (masterCipher == null) {
136+
report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>())
137+
.add("Master Cipher configuration missing");
138+
} else {
139+
if (!cipher.availableCiphers().contains(masterCipher)) {
140+
report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>())
141+
.add("Master Cipher " + masterCipher + " not supported");
142+
} else {
143+
report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.INFO, k -> new ArrayList<>())
144+
.add("Master Cipher " + masterCipher + " supported");
145+
}
146+
}
147+
String masterSource = config.get(CONF_MASTER_SOURCE);
148+
if (masterSource == null) {
149+
report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>())
150+
.add("Master Source configuration missing");
151+
} else {
152+
SecDispatcher.ValidationResponse masterSourceResponse = null;
153+
for (MasterSource masterPasswordSource : masterSources.values()) {
154+
masterSourceResponse = masterPasswordSource.validateConfiguration(masterSource);
155+
if (masterSourceResponse != null) {
156+
break;
157+
}
158+
}
159+
if (masterSourceResponse == null) {
160+
report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>())
161+
.add("Master Source configuration `" + masterSource + "` not handled");
162+
} else {
163+
subsystems.add(masterSourceResponse);
164+
if (!masterSourceResponse.isValid()) {
165+
report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>())
166+
.add("Master Source configuration `" + masterSource + "` invalid");
167+
} else {
168+
report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.INFO, k -> new ArrayList<>())
169+
.add("Master Source configuration `" + masterSource + "` valid");
170+
valid = true;
171+
}
172+
}
173+
}
174+
return new SecDispatcher.ValidationResponse(getClass().getSimpleName(), valid, report, subsystems);
175+
}
176+
128177
private String getMasterPassword(Map<String, String> config) throws SecDispatcherException {
129178
String masterSource = config.get(CONF_MASTER_SOURCE);
130179
if (masterSource == null) {

src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/EnvMasterSource.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@
2121
import javax.inject.Named;
2222
import javax.inject.Singleton;
2323

24+
import java.util.List;
25+
import java.util.Map;
2426
import java.util.Optional;
2527

2628
import org.codehaus.plexus.components.secdispatcher.MasterSourceMeta;
29+
import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
2730
import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
2831

2932
/**
@@ -58,4 +61,26 @@ protected String doHandle(String transformed) throws SecDispatcherException {
5861
}
5962
return value;
6063
}
64+
65+
@Override
66+
protected SecDispatcher.ValidationResponse doValidateConfiguration(String transformed) {
67+
String value = System.getenv(transformed);
68+
if (value == null) {
69+
return new SecDispatcher.ValidationResponse(
70+
getClass().getSimpleName(),
71+
true,
72+
Map.of(
73+
SecDispatcher.ValidationResponse.Level.WARNING,
74+
List.of("Configured environment variable not exist")),
75+
List.of());
76+
} else {
77+
return new SecDispatcher.ValidationResponse(
78+
getClass().getSimpleName(),
79+
true,
80+
Map.of(
81+
SecDispatcher.ValidationResponse.Level.INFO,
82+
List.of("Configured environment variable exist")),
83+
List.of());
84+
}
85+
}
6186
}

src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/GpgAgentMasterSource.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,17 @@
2929
import java.net.UnixDomainSocketAddress;
3030
import java.nio.channels.Channels;
3131
import java.nio.channels.SocketChannel;
32+
import java.nio.file.Files;
3233
import java.nio.file.Path;
3334
import java.nio.file.Paths;
35+
import java.util.ArrayList;
36+
import java.util.HashMap;
3437
import java.util.HexFormat;
38+
import java.util.List;
3539
import java.util.Optional;
3640

3741
import org.codehaus.plexus.components.secdispatcher.MasterSourceMeta;
42+
import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
3843
import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
3944

4045
/**
@@ -83,6 +88,39 @@ protected String doHandle(String transformed) throws SecDispatcherException {
8388
}
8489
}
8590

91+
@Override
92+
protected SecDispatcher.ValidationResponse doValidateConfiguration(String transformed) {
93+
HashMap<SecDispatcher.ValidationResponse.Level, List<String>> report = new HashMap<>();
94+
boolean valid = false;
95+
96+
String extra = "";
97+
if (transformed.contains("?")) {
98+
extra = transformed.substring(transformed.indexOf("?"));
99+
transformed = transformed.substring(0, transformed.indexOf("?"));
100+
}
101+
Path socketLocation = Paths.get(transformed);
102+
if (!socketLocation.isAbsolute()) {
103+
socketLocation = Paths.get(System.getProperty("user.home"))
104+
.resolve(socketLocation)
105+
.toAbsolutePath();
106+
}
107+
if (Files.exists(socketLocation)) {
108+
report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>())
109+
.add("Unix domain socket for GPG Agent does not exist. Maybe you need to start gpg-agent?");
110+
} else {
111+
report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.INFO, k -> new ArrayList<>())
112+
.add("Unix domain socket for GPG Agent exist");
113+
valid = true;
114+
}
115+
boolean interactive = !extra.contains("non-interactive");
116+
if (!interactive) {
117+
report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.WARNING, k -> new ArrayList<>())
118+
.add(
119+
"Non-interactive flag found, gpg-agent will not ask for passphrase, it can use only cached ones");
120+
}
121+
return new SecDispatcher.ValidationResponse(getClass().getSimpleName(), valid, report, List.of());
122+
}
123+
86124
private String load(Path socketPath, boolean interactive) throws IOException {
87125
try (SocketChannel sock = SocketChannel.open(StandardProtocolFamily.UNIX)) {
88126
sock.connect(UnixDomainSocketAddress.of(socketPath));

src/main/java/org/codehaus/plexus/components/secdispatcher/internal/sources/MasterSourceSupport.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.function.Predicate;
2323

2424
import org.codehaus.plexus.components.secdispatcher.MasterSource;
25+
import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
2526
import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
2627

2728
import static java.util.Objects.requireNonNull;
@@ -47,4 +48,13 @@ public String handle(String masterSource) throws SecDispatcherException {
4748
}
4849

4950
protected abstract String doHandle(String transformed) throws SecDispatcherException;
51+
52+
public SecDispatcher.ValidationResponse validateConfiguration(String masterSource) {
53+
if (matcher.test(masterSource)) {
54+
return doValidateConfiguration(transformer.apply(masterSource));
55+
}
56+
return null;
57+
}
58+
59+
protected abstract SecDispatcher.ValidationResponse doValidateConfiguration(String transformed);
5060
}

0 commit comments

Comments
 (0)