Skip to content

Commit 47cbecf

Browse files
kwincstamas
andauthored
Master source lookup dispatcher (#93)
This closes #90 --------- Co-authored-by: Tamas Cservenak <[email protected]>
1 parent 35ebb3d commit 47cbecf

File tree

3 files changed

+191
-2
lines changed

3 files changed

+191
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* This program is licensed to you under the Apache License Version 2.0,
3+
* and you may not use this file except in compliance with the Apache License Version 2.0.
4+
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
5+
*
6+
* Unless required by applicable law or agreed to in writing,
7+
* software distributed under the Apache License Version 2.0 is distributed on an
8+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
10+
*/
11+
12+
package org.codehaus.plexus.components.secdispatcher.internal.dispatchers;
13+
14+
import javax.inject.Inject;
15+
import javax.inject.Named;
16+
import javax.inject.Singleton;
17+
18+
import java.util.Collection;
19+
import java.util.Collections;
20+
import java.util.List;
21+
import java.util.Map;
22+
import java.util.Objects;
23+
import java.util.Optional;
24+
25+
import org.codehaus.plexus.components.secdispatcher.Dispatcher;
26+
import org.codehaus.plexus.components.secdispatcher.DispatcherMeta;
27+
import org.codehaus.plexus.components.secdispatcher.MasterSource;
28+
import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
29+
import org.codehaus.plexus.components.secdispatcher.SecDispatcher.ValidationResponse.Level;
30+
import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
31+
32+
/**
33+
* This dispatcher does not actually perform any crypto operations, but just forwards the string to be decrypted
34+
* to a {@link MasterSource}. The given string is supposed to contain a valid source reference which is resolvable
35+
* by one of the bound {@link MasterSource} implementations (and not actually an encrypted value).
36+
* This dispatcher doesn't support encryption, but just validates and returns the given master source reference.
37+
*/
38+
@Singleton
39+
@Named(MasterSourceLookupDispatcher.NAME)
40+
public class MasterSourceLookupDispatcher implements Dispatcher, DispatcherMeta {
41+
public static final String NAME = "masterSourceLookup";
42+
43+
protected final Collection<MasterSource> sources;
44+
45+
@Inject
46+
public MasterSourceLookupDispatcher(Collection<MasterSource> sources) {
47+
this.sources = sources;
48+
}
49+
50+
@Override
51+
public String name() {
52+
return NAME;
53+
}
54+
55+
@Override
56+
public String displayName() {
57+
return "Master Source Lookup Dispatcher";
58+
}
59+
60+
@Override
61+
public Collection<Field> fields() {
62+
return Collections.emptyList();
63+
}
64+
65+
@Override
66+
public EncryptPayload encrypt(String str, Map<String, String> attributes, Map<String, String> config)
67+
throws SecDispatcherException {
68+
// just make sure the given string is a valid reference!
69+
decrypt(str, attributes, config);
70+
return new EncryptPayload(attributes, str);
71+
}
72+
73+
@Override
74+
public String decrypt(String str, Map<String, String> attributes, Map<String, String> config)
75+
throws SecDispatcherException {
76+
Optional<String> plain = sources.stream()
77+
.map(source -> source.handle(str))
78+
.filter(Objects::nonNull)
79+
.findFirst();
80+
if (plain.isPresent()) {
81+
return plain.get();
82+
} else {
83+
throw new SecDispatcherException("No master source found for : " + str);
84+
}
85+
}
86+
87+
@Override
88+
public SecDispatcher.ValidationResponse validateConfiguration(Map<String, String> config) {
89+
// there is nothing really to validate without having a master reference at hand (which is outside the config)
90+
Map<Level, List<String>> report = Collections.singletonMap(
91+
SecDispatcher.ValidationResponse.Level.INFO, List.of("Configured Source configuration valid"));
92+
return new SecDispatcher.ValidationResponse(getClass().getSimpleName(), true, report, Collections.emptyList());
93+
}
94+
}

src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java

+28-2
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@
1818
import java.nio.file.Files;
1919
import java.nio.file.Path;
2020
import java.nio.file.Paths;
21+
import java.util.Collections;
22+
import java.util.List;
2123
import java.util.Map;
2224

2325
import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
2426
import org.codehaus.plexus.components.secdispatcher.internal.cipher.AESGCMNoPadding;
2527
import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.LegacyDispatcher;
2628
import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.MasterDispatcher;
29+
import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.MasterSourceLookupDispatcher;
2730
import org.codehaus.plexus.components.secdispatcher.internal.sources.EnvMasterSource;
2831
import org.codehaus.plexus.components.secdispatcher.internal.sources.GpgAgentMasterSource;
2932
import org.codehaus.plexus.components.secdispatcher.internal.sources.SystemPropertyMasterSource;
@@ -80,6 +83,18 @@ void masterWithSystemPropertyRoundTrip() throws Exception {
8083
roundtrip();
8184
}
8285

86+
@Test
87+
void masterSourceLookupWithEnvDecrypt() throws Exception {
88+
saveSec("masterSourceLookup", Collections.emptyMap());
89+
assertDecrypted("{[name=masterSourceLookup,version=something]env:MASTER_PASSWORD}", "masterPw");
90+
}
91+
92+
@Test
93+
void masterSourceLookupWithSystemPropertyDecrypt() throws Exception {
94+
saveSec("masterSourceLookup", Collections.emptyMap());
95+
assertDecrypted("{[name=masterSourceLookup,version=something]system-property:masterPassword}", "masterPw");
96+
}
97+
8398
@Test
8499
void validate() throws Exception {
85100
saveSec("master", Map.of("source", "system-property:masterPassword", "cipher", AESGCMNoPadding.CIPHER_ALG));
@@ -157,7 +172,7 @@ void detection() {
157172
protected void roundtrip() throws Exception {
158173
DefaultSecDispatcher sd = construct();
159174

160-
assertEquals(2, sd.availableDispatchers().size());
175+
assertEquals(3, sd.availableDispatchers().size());
161176
String encrypted = sd.encrypt("supersecret", Map.of(SecDispatcher.DISPATCHER_NAME_ATTR, "master", "a", "b"));
162177
// example:
163178
// {[name=master,cipher=AES/GCM/NoPadding,a=b]vvq66pZ7rkvzSPStGTI9q4QDnsmuDwo+LtjraRel2b0XpcGJFdXcYAHAS75HUA6GLpcVtEkmyQ==}
@@ -170,6 +185,14 @@ protected void roundtrip() throws Exception {
170185
assertEquals("supersecret", pass);
171186
}
172187

188+
protected void assertDecrypted(String encrypted, String expectedPlainText) throws Exception {
189+
DefaultSecDispatcher sd = construct();
190+
191+
assertEquals(3, sd.availableDispatchers().size());
192+
String plainText = sd.decrypt(encrypted);
193+
assertEquals(expectedPlainText, plainText);
194+
}
195+
173196
protected DefaultSecDispatcher construct() {
174197
return new DefaultSecDispatcher(
175198
Map.of(
@@ -184,7 +207,10 @@ protected DefaultSecDispatcher construct() {
184207
GpgAgentMasterSource.NAME,
185208
new GpgAgentMasterSource())),
186209
"legacy",
187-
new LegacyDispatcher()),
210+
new LegacyDispatcher(),
211+
"masterSourceLookup",
212+
new MasterSourceLookupDispatcher(List.of(
213+
new EnvMasterSource(), new SystemPropertyMasterSource(), new GpgAgentMasterSource()))),
188214
CONFIG_PATH);
189215
}
190216

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* This program is licensed to you under the Apache License Version 2.0,
3+
* and you may not use this file except in compliance with the Apache License Version 2.0.
4+
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
5+
*
6+
* Unless required by applicable law or agreed to in writing,
7+
* software distributed under the Apache License Version 2.0 is distributed on an
8+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
10+
*/
11+
12+
package org.codehaus.plexus.components.secdispatcher.internal.dispatchers;
13+
14+
import java.util.Collections;
15+
import java.util.Map;
16+
17+
import org.codehaus.plexus.components.secdispatcher.Dispatcher.EncryptPayload;
18+
import org.codehaus.plexus.components.secdispatcher.SecDispatcher.ValidationResponse;
19+
import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
20+
import org.codehaus.plexus.components.secdispatcher.internal.sources.EnvMasterSource;
21+
import org.codehaus.plexus.components.secdispatcher.internal.sources.SystemPropertyMasterSource;
22+
import org.junit.jupiter.api.Test;
23+
24+
import static org.junit.jupiter.api.Assertions.assertEquals;
25+
import static org.junit.jupiter.api.Assertions.assertThrows;
26+
import static org.junit.jupiter.api.Assertions.assertTrue;
27+
28+
public class MasterSourceLookupDispatcherTest {
29+
30+
@Test
31+
void testUnknownPrefix() {
32+
MasterSourceLookupDispatcher masterSourceLookupDispatcher =
33+
new MasterSourceLookupDispatcher(Collections.singleton(new EnvMasterSource()));
34+
assertThrows(
35+
SecDispatcherException.class,
36+
() -> masterSourceLookupDispatcher.decrypt("unknown-prefix:test", Map.of(), Map.of()));
37+
assertThrows(
38+
SecDispatcherException.class,
39+
() -> masterSourceLookupDispatcher.encrypt("unknown-prefix:test", Map.of(), Map.of()));
40+
}
41+
42+
@Test
43+
void testSystemPropertyMasterSourceDecrypt() {
44+
System.setProperty("myprop", "plaintext");
45+
MasterSourceLookupDispatcher masterSourceLookupDispatcher =
46+
new MasterSourceLookupDispatcher(Collections.singleton(new SystemPropertyMasterSource()));
47+
// SecDispatcher "un decorates" the PW
48+
String cleartext = masterSourceLookupDispatcher.decrypt("system-property:myprop", Map.of(), Map.of());
49+
assertEquals("plaintext", cleartext);
50+
}
51+
52+
@Test
53+
void testEncrypt() {
54+
System.setProperty("myprop", "plaintext");
55+
MasterSourceLookupDispatcher masterSourceLookupDispatcher =
56+
new MasterSourceLookupDispatcher(Collections.singleton(new SystemPropertyMasterSource()));
57+
// SecDispatcher "un decorates" the PW
58+
EncryptPayload payload = masterSourceLookupDispatcher.encrypt("system-property:myprop", Map.of(), Map.of());
59+
assertEquals("system-property:myprop", payload.getEncrypted());
60+
}
61+
62+
@Test
63+
void testValidateConfiguration() {
64+
MasterSourceLookupDispatcher masterSourceLookupDispatcher =
65+
new MasterSourceLookupDispatcher(Collections.singleton(new SystemPropertyMasterSource()));
66+
ValidationResponse response = masterSourceLookupDispatcher.validateConfiguration(Collections.emptyMap());
67+
assertTrue(response.isValid());
68+
}
69+
}

0 commit comments

Comments
 (0)