diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/ForwardingDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/ForwardingDispatcher.java deleted file mode 100644 index e9c5fa5..0000000 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/ForwardingDispatcher.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (c) 2008 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ - -package org.codehaus.plexus.components.secdispatcher.internal.dispatchers; - -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.codehaus.plexus.components.secdispatcher.Dispatcher; -import org.codehaus.plexus.components.secdispatcher.DispatcherMeta; -import org.codehaus.plexus.components.secdispatcher.MasterSource; -import org.codehaus.plexus.components.secdispatcher.MasterSourceMeta; -import org.codehaus.plexus.components.secdispatcher.SecDispatcher; -import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; - -/** - * This dispatcher forwards requests fully to defined sources. - */ -@Singleton -@Named(ForwardingDispatcher.NAME) -public class ForwardingDispatcher implements Dispatcher, DispatcherMeta { - public static final String NAME = "forwarding"; - - private static final String CONF_SOURCE = "source"; - - protected final Map sources; - - @Inject - public ForwardingDispatcher(Map sources) { - this.sources = sources; - } - - @Override - public String name() { - return NAME; - } - - @Override - public String displayName() { - return "Forwarding Password Dispatcher"; - } - - @Override - public Collection fields() { - return List.of(Field.builder(CONF_SOURCE) - .optional(false) - .description("Source of the password") - .options(sources.entrySet().stream() - .map(e -> { - MasterSource ms = e.getValue(); - if (ms instanceof MasterSourceMeta m) { - Field.Builder b = Field.builder(e.getKey()).description(m.description()); - if (m.configTemplate().isPresent()) { - b.defaultValue(m.configTemplate().get()); - } - return b.build(); - } else { - return Field.builder(e.getKey()) - .description(e.getKey() + "(Field not described, needs manual configuration)") - .build(); - } - }) - .toList()) - .build()); - } - - @Override - public EncryptPayload encrypt(String str, Map attributes, Map config) - throws SecDispatcherException { - throw new UnsupportedOperationException("Forwarding dispatcher does not support encryption"); - } - - @Override - public String decrypt(String str, Map attributes, Map config) - throws SecDispatcherException { - MasterSource masterSource = getPasswordSource(config); - return masterSource.handle(str); - } - - @Override - public SecDispatcher.ValidationResponse validateConfiguration(Map config) { - HashMap> report = new HashMap<>(); - ArrayList subsystems = new ArrayList<>(); - boolean valid = false; - String masterSource = config.get(CONF_SOURCE); - if (masterSource == null) { - report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) - .add("Source configuration missing"); - } else { - SecDispatcher.ValidationResponse masterSourceResponse = null; - for (MasterSource masterPasswordSource : sources.values()) { - masterSourceResponse = masterPasswordSource.validateConfiguration(masterSource); - if (masterSourceResponse != null) { - break; - } - } - if (masterSourceResponse == null) { - report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) - .add("Configured Source configuration not handled"); - } else { - subsystems.add(masterSourceResponse); - if (!masterSourceResponse.isValid()) { - report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) - .add("Configured Source configuration invalid"); - } else { - report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.INFO, k -> new ArrayList<>()) - .add("Configured Source configuration valid"); - valid = true; - } - } - } - return new SecDispatcher.ValidationResponse(getClass().getSimpleName(), valid, report, subsystems); - } - - protected MasterSource getPasswordSource(Map config) throws SecDispatcherException { - String masterSource = config.get(CONF_SOURCE); - if (masterSource == null) { - throw new SecDispatcherException("Invalid configuration: Missing configuration " + CONF_SOURCE); - } - MasterSource source = sources.get(masterSource); - if (source != null) { - return source; - } - throw new SecDispatcherException("No source found the given masterSource: " + masterSource); - } -} diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterSourceLookupDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterSourceLookupDispatcher.java new file mode 100644 index 0000000..05e08a2 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterSourceLookupDispatcher.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2008 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package org.codehaus.plexus.components.secdispatcher.internal.dispatchers; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +import org.codehaus.plexus.components.secdispatcher.Dispatcher; +import org.codehaus.plexus.components.secdispatcher.DispatcherMeta; +import org.codehaus.plexus.components.secdispatcher.MasterSource; +import org.codehaus.plexus.components.secdispatcher.SecDispatcher; +import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; + +/** + * This dispatcher does not actually do any crypto operations, but just forwards the string to be decrypted + * to a {@link MasterSource}. The given string is supposed to contain a valid source reference which is resolvable + * by one of the bound {@link MasterSource} implementations (and not actually an encrypted value). + * This dispatcher doesn't support encrypting but just returns a given master source + */ +@Singleton +@Named(MasterSourceLookupDispatcher.NAME) +public class MasterSourceLookupDispatcher implements Dispatcher, DispatcherMeta { + public static final String NAME = "masterSourceLookup"; + + protected final Collection sources; + + @Inject + public MasterSourceLookupDispatcher(Collection sources) { + this.sources = sources; + } + + @Override + public String name() { + return NAME; + } + + @Override + public String displayName() { + return "Master Source Lookup Dispatcher"; + } + + @Override + public Collection fields() { + return Collections.emptyList(); + } + + @Override + public EncryptPayload encrypt(String str, Map attributes, Map config) + throws SecDispatcherException { + // just make sure the given string is a valid reference! + decrypt(str, attributes, config); + return new EncryptPayload(attributes, str); + } + + @Override + public String decrypt(String str, Map attributes, Map config) + throws SecDispatcherException { + Optional plain = sources.stream() + .map(source -> source.handle(str)) + .filter(Objects::nonNull) + .findFirst(); + if (plain.isPresent()) { + return plain.get(); + } else { + throw new SecDispatcherException("No master source found for : " + str); + } + } + + @Override + public SecDispatcher.ValidationResponse validateConfiguration(Map config) { + // there is nothing really to validate without having a master reference at hand (which is outside the config) + HashMap> report = new HashMap<>(); + ArrayList subsystems = new ArrayList<>(); + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.INFO, k -> new ArrayList<>()) + .add("Configured Source configuration valid"); + return new SecDispatcher.ValidationResponse(getClass().getSimpleName(), true, report, subsystems); + } +} diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java index 21442fe..0723d2d 100644 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java @@ -18,13 +18,15 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; import java.util.Map; import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.internal.cipher.AESGCMNoPadding; -import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.ForwardingDispatcher; import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.LegacyDispatcher; import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.MasterDispatcher; +import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.MasterSourceLookupDispatcher; import org.codehaus.plexus.components.secdispatcher.internal.sources.EnvMasterSource; import org.codehaus.plexus.components.secdispatcher.internal.sources.GpgAgentMasterSource; import org.codehaus.plexus.components.secdispatcher.internal.sources.SystemPropertyMasterSource; @@ -83,14 +85,14 @@ void masterWithSystemPropertyRoundTrip() throws Exception { @Test void forwardingWithEnvDecrypt() throws Exception { - saveSec("forwarding", Map.of("source", "env")); - decryptForwarding("{[name=forwarding,version=something]env:MASTER_PASSWORD}", "masterPw"); + saveSec("masterSourceLookup", Collections.emptyMap()); + decryptForwarding("{[name=masterSourceLookup,version=something]env:MASTER_PASSWORD}", "masterPw"); } @Test void forwardingWithSystemPropertyDecrypt() throws Exception { - saveSec("forwarding", Map.of("source", "system-property")); - decryptForwarding("{[name=forwarding,version=something]system-property:masterPassword}", "masterPw"); + saveSec("masterSourceLookup", Collections.emptyMap()); + decryptForwarding("{[name=masterSourceLookup,version=something]system-property:masterPassword}", "masterPw"); } @Test @@ -206,14 +208,9 @@ protected DefaultSecDispatcher construct() { new GpgAgentMasterSource())), "legacy", new LegacyDispatcher(), - "forwarding", - new ForwardingDispatcher(Map.of( - EnvMasterSource.NAME, - new EnvMasterSource(), - SystemPropertyMasterSource.NAME, - new SystemPropertyMasterSource(), - GpgAgentMasterSource.NAME, - new GpgAgentMasterSource()))), + "masterSourceLookup", + new MasterSourceLookupDispatcher(List.of( + new EnvMasterSource(), new SystemPropertyMasterSource(), new GpgAgentMasterSource()))), CONFIG_PATH); } diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterSourceLookupDispatcherTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterSourceLookupDispatcherTest.java new file mode 100644 index 0000000..7c4fae6 --- /dev/null +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/MasterSourceLookupDispatcherTest.java @@ -0,0 +1,69 @@ +/* + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package org.codehaus.plexus.components.secdispatcher.internal.dispatchers; + +import java.util.Collections; +import java.util.Map; + +import org.codehaus.plexus.components.secdispatcher.Dispatcher.EncryptPayload; +import org.codehaus.plexus.components.secdispatcher.SecDispatcher.ValidationResponse; +import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; +import org.codehaus.plexus.components.secdispatcher.internal.sources.EnvMasterSource; +import org.codehaus.plexus.components.secdispatcher.internal.sources.SystemPropertyMasterSource; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class MasterSourceLookupDispatcherTest { + + @Test + void testUnknownPrefix() { + MasterSourceLookupDispatcher masterSourceLookupDispatcher = + new MasterSourceLookupDispatcher(Collections.singleton(new EnvMasterSource())); + assertThrows( + SecDispatcherException.class, + () -> masterSourceLookupDispatcher.decrypt("unknown-prefix:test", Map.of(), Map.of())); + assertThrows( + SecDispatcherException.class, + () -> masterSourceLookupDispatcher.encrypt("unknown-prefix:test", Map.of(), Map.of())); + } + + @Test + void testSystemPropertyMasterSourceDecrypt() { + System.setProperty("myprop", "plaintext"); + MasterSourceLookupDispatcher masterSourceLookupDispatcher = + new MasterSourceLookupDispatcher(Collections.singleton(new SystemPropertyMasterSource())); + // SecDispatcher "un decorates" the PW + String cleartext = masterSourceLookupDispatcher.decrypt("system-property:myprop", Map.of(), Map.of()); + assertEquals("plaintext", cleartext); + } + + @Test + void testEncrypt() { + System.setProperty("myprop", "plaintext"); + MasterSourceLookupDispatcher masterSourceLookupDispatcher = + new MasterSourceLookupDispatcher(Collections.singleton(new SystemPropertyMasterSource())); + // SecDispatcher "un decorates" the PW + EncryptPayload payload = masterSourceLookupDispatcher.encrypt("system-property:myprop", Map.of(), Map.of()); + assertEquals("system-property:myprop", payload.getEncrypted()); + } + + @Test + void testValidateConfiguration() { + MasterSourceLookupDispatcher masterSourceLookupDispatcher = + new MasterSourceLookupDispatcher(Collections.singleton(new SystemPropertyMasterSource())); + ValidationResponse response = masterSourceLookupDispatcher.validateConfiguration(Collections.emptyMap()); + assertTrue(response.isValid()); + } +}