1
2
3
4
5
6
7
8
9
10
11
12
13
14 package org.codehaus.plexus.components.secdispatcher.internal;
15
16 import java.io.IOException;
17 import java.nio.file.Files;
18 import java.nio.file.Path;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.StringTokenizer;
26 import java.util.stream.Collectors;
27
28 import org.codehaus.plexus.components.secdispatcher.Dispatcher;
29 import org.codehaus.plexus.components.secdispatcher.DispatcherMeta;
30 import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
31 import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
32 import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.LegacyDispatcher;
33 import org.codehaus.plexus.components.secdispatcher.model.SettingsSecurity;
34
35 import static java.util.Objects.requireNonNull;
36
37
38
39
40
41
42
43
44
45
46
47
48 public class DefaultSecDispatcher implements SecDispatcher {
49 public static final String SHIELD_BEGIN = "{";
50 public static final String SHIELD_END = "}";
51 public static final String ATTR_START = "[";
52 public static final String ATTR_STOP = "]";
53
54 protected final Map<String, Dispatcher> dispatchers;
55 protected final Path configurationFile;
56
57 public DefaultSecDispatcher(Map<String, Dispatcher> dispatchers, Path configurationFile) {
58 this.dispatchers = requireNonNull(dispatchers);
59 this.configurationFile = requireNonNull(configurationFile);
60
61
62 if (Files.isDirectory(configurationFile)) {
63 throw new IllegalArgumentException("configurationFile cannot be a directory");
64 }
65 }
66
67 @Override
68 public Set<DispatcherMeta> availableDispatchers() {
69 return Set.copyOf(
70 dispatchers.entrySet().stream().map(this::dispatcherMeta).collect(Collectors.toSet()));
71 }
72
73 private DispatcherMeta dispatcherMeta(Map.Entry<String, Dispatcher> dispatcher) {
74
75 Dispatcher d = dispatcher.getValue();
76 if (d instanceof DispatcherMeta meta) {
77 return meta;
78 } else {
79 return new DispatcherMeta() {
80 @Override
81 public String name() {
82 return dispatcher.getKey();
83 }
84
85 @Override
86 public String displayName() {
87 return dispatcher.getKey() + " (needs manual configuration)";
88 }
89
90 @Override
91 public Collection<Field> fields() {
92 return List.of();
93 }
94 };
95 }
96 }
97
98 @Override
99 public String encrypt(String str, Map<String, String> attr) throws SecDispatcherException, IOException {
100 if (isEncryptedString(str)) return str;
101 if (attr == null) {
102 attr = new HashMap<>();
103 } else {
104 attr = new HashMap<>(attr);
105 }
106 if (attr.get(DISPATCHER_NAME_ATTR) == null) {
107 SettingsSecurity conf = readConfiguration(false);
108 if (conf == null) {
109 throw new SecDispatcherException("No configuration found");
110 }
111 String defaultDispatcher = conf.getDefaultDispatcher();
112 if (defaultDispatcher == null) {
113 throw new SecDispatcherException("No defaultDispatcher set in configuration");
114 }
115 attr.put(DISPATCHER_NAME_ATTR, defaultDispatcher);
116 }
117 String name = attr.get(DISPATCHER_NAME_ATTR);
118 Dispatcher dispatcher = dispatchers.get(name);
119 if (dispatcher == null) throw new SecDispatcherException("No dispatcher exist with name " + name);
120 Dispatcher.EncryptPayload payload = dispatcher.encrypt(str, attr, prepareDispatcherConfig(name));
121 HashMap<String, String> resultAttributes = new HashMap<>(payload.getAttributes());
122 resultAttributes.put(SecDispatcher.DISPATCHER_NAME_ATTR, name);
123 resultAttributes.put(SecDispatcher.DISPATCHER_VERSION_ATTR, SecUtil.specVersion());
124 return SHIELD_BEGIN
125 + ATTR_START
126 + resultAttributes.entrySet().stream()
127 .map(e -> e.getKey() + "=" + e.getValue())
128 .collect(Collectors.joining(","))
129 + ATTR_STOP
130 + payload.getEncrypted()
131 + SHIELD_END;
132 }
133
134 @Override
135 public String decrypt(String str) throws SecDispatcherException, IOException {
136 String bare;
137 Map<String, String> attr;
138 if (isLegacyEncryptedString(str)) {
139 bare = unDecorateLegacy(str);
140 attr = new HashMap<>();
141 attr.put(DISPATCHER_NAME_ATTR, LegacyDispatcher.NAME);
142 } else if (isEncryptedString(str)) {
143 bare = unDecorate(str);
144 attr = requireNonNull(stripAttributes(bare));
145 } else {
146 return str;
147 }
148 String name = attr.get(DISPATCHER_NAME_ATTR);
149 Dispatcher dispatcher = dispatchers.get(name);
150 if (dispatcher == null) throw new SecDispatcherException("No dispatcher exist with name " + name);
151 return dispatcher.decrypt(strip(bare), attr, prepareDispatcherConfig(name));
152 }
153
154
155
156
157
158
159 @Override
160 public boolean isEncryptedString(String str) {
161 boolean looksLike = str != null
162 && !str.isBlank()
163 && str.startsWith(SHIELD_BEGIN)
164 && str.endsWith(SHIELD_END)
165 && !unDecorate(str).contains(SHIELD_BEGIN)
166 && !unDecorate(str).contains(SHIELD_END);
167 if (looksLike) {
168 Map<String, String> attributes = stripAttributes(unDecorate(str));
169 return attributes.containsKey(DISPATCHER_NAME_ATTR) && attributes.containsKey(DISPATCHER_VERSION_ATTR);
170 }
171 return false;
172 }
173
174
175
176
177
178
179 @Override
180 public boolean isLegacyEncryptedString(String str) {
181 if (str != null && str.contains(SHIELD_BEGIN)) {
182 str = str.substring(str.indexOf(SHIELD_BEGIN));
183 if (str.contains(SHIELD_END)) {
184 str = str.substring(0, str.indexOf(SHIELD_END) + 1);
185 String undecorated = unDecorate(str);
186 boolean looksLike = !str.isBlank()
187 && str.startsWith(SHIELD_BEGIN)
188 && str.endsWith(SHIELD_END)
189 && !undecorated.contains(SHIELD_BEGIN)
190 && !undecorated.contains(SHIELD_END);
191 if (looksLike) {
192 return stripAttributes(undecorated).isEmpty();
193 }
194 }
195 }
196 return false;
197 }
198
199 @Override
200 public SettingsSecurity readConfiguration(boolean createIfMissing) throws IOException {
201 SettingsSecurity configuration = SecUtil.read(configurationFile);
202 if (configuration == null && createIfMissing) {
203 configuration = new SettingsSecurity();
204 }
205 return configuration;
206 }
207
208 @Override
209 public void writeConfiguration(SettingsSecurity configuration) throws IOException {
210 requireNonNull(configuration, "configuration is null");
211 SecUtil.write(configurationFile, configuration, true);
212 }
213
214 @Override
215 public ValidationResponse validateConfiguration() {
216 HashMap<ValidationResponse.Level, List<String>> report = new HashMap<>();
217 ArrayList<ValidationResponse> subsystems = new ArrayList<>();
218 boolean valid = false;
219 try {
220 SettingsSecurity config = readConfiguration(false);
221 if (config == null) {
222 report.computeIfAbsent(ValidationResponse.Level.ERROR, k -> new ArrayList<>())
223 .add("No configuration file found on path " + configurationFile);
224 } else {
225 report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>())
226 .add("Configuration file present on path " + configurationFile);
227 String defaultDispatcher = config.getDefaultDispatcher();
228 if (defaultDispatcher == null) {
229 report.computeIfAbsent(ValidationResponse.Level.ERROR, k -> new ArrayList<>())
230 .add("No default dispatcher set in configuration");
231 } else {
232 report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>())
233 .add("Default dispatcher configured");
234 Dispatcher dispatcher = dispatchers.get(defaultDispatcher);
235 if (dispatcher == null) {
236 report.computeIfAbsent(ValidationResponse.Level.ERROR, k -> new ArrayList<>())
237 .add("Configured default dispatcher not present in system");
238 } else {
239 ValidationResponse dispatcherResponse =
240 dispatcher.validateConfiguration(prepareDispatcherConfig(defaultDispatcher));
241 subsystems.add(dispatcherResponse);
242 if (!dispatcherResponse.isValid()) {
243 report.computeIfAbsent(ValidationResponse.Level.ERROR, k -> new ArrayList<>())
244 .add("Configured default dispatcher configuration is invalid");
245 } else {
246 valid = true;
247 report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>())
248 .add("Configured default dispatcher configuration is valid");
249 }
250 }
251 }
252 }
253
254
255 Dispatcher legacy = dispatchers.get(LegacyDispatcher.NAME);
256 if (legacy == null) {
257 report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>())
258 .add("Legacy dispatcher not present in system");
259 } else {
260 report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>())
261 .add("Legacy dispatcher present in system");
262 ValidationResponse legacyResponse =
263 legacy.validateConfiguration(prepareDispatcherConfig(LegacyDispatcher.NAME));
264 subsystems.add(legacyResponse);
265 if (!legacyResponse.isValid()) {
266 report.computeIfAbsent(ValidationResponse.Level.WARNING, k -> new ArrayList<>())
267 .add("Legacy dispatcher not operational; transparent fallback not possible");
268 } else {
269 report.computeIfAbsent(ValidationResponse.Level.INFO, k -> new ArrayList<>())
270 .add("Legacy dispatcher is operational; transparent fallback possible");
271 }
272 }
273 } catch (IOException e) {
274 report.computeIfAbsent(ValidationResponse.Level.ERROR, k -> new ArrayList<>())
275 .add(e.getMessage());
276 }
277
278 return new ValidationResponse(getClass().getSimpleName(), valid, report, subsystems);
279 }
280
281 protected Map<String, String> prepareDispatcherConfig(String name) throws IOException {
282 HashMap<String, String> dispatcherConf = new HashMap<>();
283 Map<String, String> conf = SecUtil.getConfig(SecUtil.read(configurationFile), name);
284 if (conf != null) {
285 dispatcherConf.putAll(conf);
286 }
287 return dispatcherConf;
288 }
289
290 protected String strip(String str) {
291 int start = str.indexOf(ATTR_START);
292 int stop = str.indexOf(ATTR_STOP);
293 if (start != -1 && stop != -1 && stop > start) {
294 return str.substring(stop + 1);
295 }
296 return str;
297 }
298
299 protected Map<String, String> stripAttributes(String str) {
300 HashMap<String, String> result = new HashMap<>();
301 int start = str.indexOf(ATTR_START);
302 int stop = str.indexOf(ATTR_STOP);
303 if (start != -1 && stop != -1 && stop > start) {
304 if (start != 0) throw new SecDispatcherException("Attributes can be prefix only");
305 if (stop == start + 1) return null;
306 String attrs = str.substring(start + 1, stop).trim();
307 if (attrs.isEmpty()) return null;
308 StringTokenizer st = new StringTokenizer(attrs, ",");
309 while (st.hasMoreTokens()) {
310 String pair = st.nextToken();
311 int pos = pair.indexOf('=');
312 if (pos == -1) throw new SecDispatcherException("Attribute malformed: " + pair);
313 String key = pair.substring(0, pos).trim();
314 String val = pair.substring(pos + 1).trim();
315 result.put(key, val);
316 }
317 }
318 return result;
319 }
320
321 protected String unDecorate(String str) {
322 return str.substring(SHIELD_BEGIN.length(), str.length() - SHIELD_END.length());
323 }
324
325 protected String unDecorateLegacy(String str) {
326 str = str.substring(str.indexOf(SHIELD_BEGIN));
327 str = str.substring(0, str.indexOf(SHIELD_END) + 1);
328 return unDecorate(str);
329 }
330 }