diff --git a/pom.xml b/pom.xml index 542ea30..02e4f4e 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,6 @@ org.apache.maven @@ -175,11 +174,6 @@ pom import - - org.junit-pioneer - junit-pioneer - 1.5.0 - org.mockito mockito-junit-jupiter @@ -222,7 +216,6 @@ - org.apache.maven.plugin-tools maven-plugin-annotations @@ -279,11 +272,6 @@ junit-jupiter test - - org.junit-pioneer - junit-pioneer - test - org.mockito mockito-junit-jupiter @@ -294,7 +282,6 @@ slf4j-mock test - @@ -358,19 +345,6 @@ true - - - - jmv-9+ - - [9,) - - - --add-opens=java.base/java.util=ALL-UNNAMED - --add-opens=java.base/java.lang=ALL-UNNAMED - ${opens.util} ${opens.lang} - - diff --git a/src/it/pom-packaging/invoker.properties b/src/it/pom-packaging/invoker.properties index 398e3b4..76673b5 100644 --- a/src/it/pom-packaging/invoker.properties +++ b/src/it/pom-packaging/invoker.properties @@ -15,6 +15,6 @@ # invoker.goals = install --no-transfer-progress -Drevision=1.1.1 -Dsettings.security=${project.basedir}/src/it/settings-security.xml invoker.mavenOpts = ${argLine} -invoker.debug=true invoker.ordinal = 100 +invoker.debug = true diff --git a/src/main/java/org/simplify4u/plugins/sign/KeyInfoFactory.java b/src/main/java/org/simplify4u/plugins/sign/KeyInfoFactory.java new file mode 100644 index 0000000..f7e269b --- /dev/null +++ b/src/main/java/org/simplify4u/plugins/sign/KeyInfoFactory.java @@ -0,0 +1,128 @@ +/* + * Copyright 2021 Slawomir Jaranowski + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.simplify4u.plugins.sign; + +import java.io.File; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Optional; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import io.vavr.control.Try; +import lombok.Builder; +import lombok.Value; +import lombok.extern.slf4j.Slf4j; +import org.simplify4u.plugins.sign.openpgp.PGPKeyInfo; +import org.simplify4u.plugins.sign.utils.Environment; +import org.simplify4u.plugins.sign.utils.FileUtil; +import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher; + +/** + * Service to build {@link PGPKeyInfo} object. + */ +@Singleton +@Named +@Slf4j +public class KeyInfoFactory { + + private static final String SIGN_KEY_ID_ENV = "SIGN_KEY_ID"; + private static final String SIGN_KEY_ENV = "SIGN_KEY"; + private static final String SIGN_KEY_PASS_ENV = "SIGN_KEY_PASS"; + + @Inject + private Environment environment; + + @Inject + private SecDispatcher secDispatcher; + + /** + * Value class for data needed to build key info. + */ + @Value + @Builder + public static class KeyInfoRequest { + String id; + String pass; + File file; + } + + /** + * Build {@link PGPKeyInfo}. + * + * @param keyInfoRequest input data for key + * + * @return a {@link PGPKeyInfo} with resolved data. + */ + public PGPKeyInfo buildKeyInfo(KeyInfoRequest keyInfoRequest) { + + return PGPKeyInfo.builder() + .id(resolveKeyId(keyInfoRequest)) + .pass(resolveKeyPass(keyInfoRequest)) + .key(resolveKey(keyInfoRequest)) + .build(); + } + + private Long resolveKeyId(KeyInfoRequest keyInfoRequest) { + return Optional.ofNullable(environment.getEnv(SIGN_KEY_ID_ENV).orElseGet(keyInfoRequest::getId)) + .map(KeyInfoFactory::parseKeyId) + .orElse(null); + } + + private String resolveKeyPass(KeyInfoRequest keyInfoRequest) { + return Optional.ofNullable(environment.getEnv(SIGN_KEY_PASS_ENV).orElseGet(keyInfoRequest::getPass)) + .map(this::decryptPass) + .orElse(null); + } + + private byte[] resolveKey(KeyInfoRequest keyInfoRequest) { + return environment.getEnv(SIGN_KEY_ENV) + .map(String::trim) + .map(KeyInfoFactory::keyFromString) + .orElseGet(() -> keyFromFile(keyInfoRequest.getFile())); + } + + private String decryptPass(String pass) { + return Try.of(() -> secDispatcher.decrypt(pass)) + .getOrElseThrow(e -> new SignMojoException("Invalid encrypted password: " + e.getMessage())); + } + + private static long parseKeyId(String key) { + return Try.of(() -> new BigInteger(key, 16)) + .map(BigInteger::longValue) + .getOrElseThrow(e -> new SignMojoException("Invalid keyId: " + e.getMessage())); + } + + private static byte[] keyFromString(String key) { + return key.getBytes(StandardCharsets.US_ASCII); + } + + private static byte[] keyFromFile(File keyFile) { + + File file = FileUtil.calculateWithUserHome(keyFile); + + if (file.exists()) { + LOGGER.debug("Read key from file: {}", file); + return Try.of(() -> Files.readAllBytes(file.toPath())).get(); + } else { + LOGGER.debug("Key file: {} not exist", keyFile); + } + + return new byte[]{}; + } +} diff --git a/src/main/java/org/simplify4u/plugins/sign/SignMojo.java b/src/main/java/org/simplify4u/plugins/sign/SignMojo.java index aba7668..b479c06 100644 --- a/src/main/java/org/simplify4u/plugins/sign/SignMojo.java +++ b/src/main/java/org/simplify4u/plugins/sign/SignMojo.java @@ -21,7 +21,6 @@ import java.util.Set; import javax.inject.Inject; -import io.vavr.control.Try; import lombok.AccessLevel; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -34,8 +33,6 @@ import org.apache.maven.project.MavenProjectHelper; import org.apache.maven.project.artifact.ProjectArtifact; import org.simplify4u.plugins.sign.openpgp.PGPKeyInfo; -import org.simplify4u.plugins.sign.openpgp.PGPSignerKeyNotFoundException; -import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher; /** * Creates Open PGP / GPG signatures for all of the project's artifacts. @@ -54,10 +51,10 @@ public class SignMojo extends AbstractMojo { private MavenProjectHelper projectHelper; @Inject - private ArtifactSignerFactory artifactSignerFactory; + private KeyInfoFactory keyInfoFactory; @Inject - private SecDispatcher secDispatcher; + private ArtifactSignerFactory artifactSignerFactory; /** *

keyId used for signing. If not provided first key from keyFile will be taken.

@@ -134,21 +131,19 @@ public void execute() { return; } - PGPKeyInfo keyInfo; - try { - keyInfo = PGPKeyInfo.builder() - .passDecryptor(this::decryptPass) - .keyId(keyId) - .keyPass(keyPass) - .keyFile(keyFile) - .build(); - } catch (PGPSignerKeyNotFoundException e) { + PGPKeyInfo keyInfo = keyInfoFactory.buildKeyInfo( + KeyInfoFactory.KeyInfoRequest.builder() + .id(keyId) + .pass(keyPass) + .file(keyFile) + .build()); + + if (!keyInfo.isKeyAvailable()) { if (skipNoKey) { LOGGER.info("Sign - key not found - skip execution"); return; - } else { - throw e; } + throw new SignMojoException("Required key for signing not found"); } ArtifactSigner artifactSigner = artifactSignerFactory.getSigner(keyInfo); @@ -167,11 +162,6 @@ public void execute() { .forEach(this::attachSignResult); } - private String decryptPass(String pass) { - return Try.of(() -> secDispatcher.decrypt(pass)) - .getOrElseThrow(e -> new SignMojoException("Invalid encrypted password", e)); - } - /** * Attache sign result to project. */ diff --git a/src/main/java/org/simplify4u/plugins/sign/SignMojoException.java b/src/main/java/org/simplify4u/plugins/sign/SignMojoException.java index f8764b7..cc5cfdb 100644 --- a/src/main/java/org/simplify4u/plugins/sign/SignMojoException.java +++ b/src/main/java/org/simplify4u/plugins/sign/SignMojoException.java @@ -33,8 +33,4 @@ public class SignMojoException extends RuntimeException { SignMojoException(String message) { super(message); } - - SignMojoException(String message, Throwable cause) { - super(message, cause); - } } diff --git a/src/main/java/org/simplify4u/plugins/sign/openpgp/PGPKeyInfo.java b/src/main/java/org/simplify4u/plugins/sign/openpgp/PGPKeyInfo.java index fbb3a65..4d349f5 100644 --- a/src/main/java/org/simplify4u/plugins/sign/openpgp/PGPKeyInfo.java +++ b/src/main/java/org/simplify4u/plugins/sign/openpgp/PGPKeyInfo.java @@ -16,20 +16,11 @@ package org.simplify4u.plugins.sign.openpgp; import java.io.ByteArrayInputStream; -import java.io.File; import java.io.InputStream; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.util.Optional; -import java.util.function.UnaryOperator; -import io.vavr.control.Try; -import lombok.AccessLevel; import lombok.Builder; -import lombok.Getter; -import lombok.experimental.FieldDefaults; -import lombok.extern.slf4j.Slf4j; +import lombok.Value; /** * Information about pgp key. @@ -37,83 +28,25 @@ * @author Slawomir Jaranowski */ -@Slf4j -@Getter -@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) +@Value +@Builder public class PGPKeyInfo { - private static final String SIGN_KEY_ID_ENV = "SIGN_KEY_ID"; - private static final String SIGN_KEY_ENV = "SIGN_KEY"; - private static final String SIGN_KEY_PASS_ENV = "SIGN_KEY_PASS"; - Long id; - char[] pass; - InputStream key; - - @Builder - private PGPKeyInfo(String keyId, String keyPass, File keyFile, UnaryOperator passDecryptor) { - - id = Optional.ofNullable(stringFromEnv(SIGN_KEY_ID_ENV).orElse(keyId)) - .map(PGPKeyInfo::parseKeyId) - .orElse(null); - - pass = Optional.ofNullable(stringFromEnv(SIGN_KEY_PASS_ENV).orElse(keyPass)) - .map(Optional.ofNullable(passDecryptor).orElseGet(UnaryOperator::identity)) - .map(String::toCharArray) - .orElse(null); + String pass; + byte[] key; - key = stringFromEnv(SIGN_KEY_ENV) - .map(String::trim) - .map(PGPKeyInfo::keyFromString) - .orElseGet(() -> keyFromFile(keyFile)); + public boolean isKeyAvailable() { + return key != null && key.length > 0; } - /** - * Read environment variable and filter by "null" string - this value is set be invoker-maven-plugin. - *

- * TODO - remove workaround after fix and release https://issues.apache.org/jira/browse/MINVOKER-273 - * - * @param environmentName a environment variable name - * - * @return content of environment variable or empty if not exist. - */ - private static Optional stringFromEnv(String environmentName) { - Optional returnValue = Optional.ofNullable(System.getenv(environmentName)) - .map(String::trim) - .filter(s -> !"null".equals(s)) - .filter(s -> !s.isEmpty()); - - if (returnValue.isPresent()) { - LOGGER.debug("Retrieved {} configuration from environment variable", environmentName); - } else { - LOGGER.debug("No {} set as environment variable", environmentName); - } - - return returnValue; + public InputStream getKeyStream() { + return new ByteArrayInputStream(key); } - private static InputStream keyFromFile(File keyFile) { - - File file = PGPKeyFileUtil.calculateWithUserHome(keyFile); - - if (!file.exists()) { - throw new PGPSignerKeyNotFoundException("key file: " + keyFile + " not found"); - } - - LOGGER.debug("Read key from file: {}", file); - - return Try.of(() -> Files.readAllBytes(file.toPath())) - .map(ByteArrayInputStream::new) - .getOrElseThrow(PGPSignerException::new); - } - - private static InputStream keyFromString(String key) { - return new ByteArrayInputStream(key.getBytes(StandardCharsets.US_ASCII)); - } - - private static long parseKeyId(String key) { - return Try.of(() -> new BigInteger(key, 16)) - .map(BigInteger::longValue) - .getOrElseThrow(e -> new PGPSignerException("Invalid keyId: " + e.getMessage())); + public char[] getPass() { + return Optional.ofNullable(pass) + .map(String::toCharArray) + .orElse(null); } } diff --git a/src/main/java/org/simplify4u/plugins/sign/openpgp/PGPSigner.java b/src/main/java/org/simplify4u/plugins/sign/openpgp/PGPSigner.java index 60b4672..46604fd 100644 --- a/src/main/java/org/simplify4u/plugins/sign/openpgp/PGPSigner.java +++ b/src/main/java/org/simplify4u/plugins/sign/openpgp/PGPSigner.java @@ -102,7 +102,7 @@ private void prepareAdditionalSubPacket() { */ private void loadKey() throws IOException, PGPException { - InputStream inputStream = PGPUtil.getDecoderStream(pgpKeyInfo.getKey()); + InputStream inputStream = PGPUtil.getDecoderStream(pgpKeyInfo.getKeyStream()); PGPSecretKeyRingCollection pgpSecretKeyRingCollection = new PGPSecretKeyRingCollection(inputStream, new JcaKeyFingerprintCalculator()); diff --git a/src/main/java/org/simplify4u/plugins/sign/utils/Environment.java b/src/main/java/org/simplify4u/plugins/sign/utils/Environment.java new file mode 100644 index 0000000..6605bcb --- /dev/null +++ b/src/main/java/org/simplify4u/plugins/sign/utils/Environment.java @@ -0,0 +1,77 @@ +/* + * Copyright 2021 Slawomir Jaranowski + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.simplify4u.plugins.sign.utils; + +import java.util.Optional; +import java.util.function.UnaryOperator; +import javax.inject.Named; +import javax.inject.Singleton; + +import lombok.extern.slf4j.Slf4j; + +/** + * Environment variable support. + */ +@Slf4j +@Named +@Singleton +public class Environment { + + /** + * Function for read environment variable. + */ + private final UnaryOperator environmentGetter; + + /** + * Default class with {@link System#getenv(String)} as variable provider. + */ + public Environment() { + this(System::getenv); + } + + /** + * For testing purpose we can provide our function or mock for reading environment variable. + * + * @param environmentGetter a environment variable provider. + */ + Environment(UnaryOperator environmentGetter) { + this.environmentGetter = environmentGetter; + } + + /** + * Read environment variable and filter by "null" string - this value is set be invoker-maven-plugin. + *

+ * TODO - remove workaround after fix and release https://issues.apache.org/jira/browse/MINVOKER-273 + * + * @param environmentName a environment variable name + * + * @return content of environment variable or empty if not exist. + */ + public Optional getEnv(String environmentName) { + Optional returnValue = Optional.ofNullable(environmentGetter.apply(environmentName)) + .map(String::trim) + .filter(s -> !"null".equals(s)) + .filter(s -> !s.isEmpty()); + + if (returnValue.isPresent()) { + LOGGER.debug("Retrieved {} configuration from environment variable", environmentName); + } else { + LOGGER.debug("No {} set as environment variable", environmentName); + } + + return returnValue; + } +} diff --git a/src/main/java/org/simplify4u/plugins/sign/openpgp/PGPKeyFileUtil.java b/src/main/java/org/simplify4u/plugins/sign/utils/FileUtil.java similarity index 90% rename from src/main/java/org/simplify4u/plugins/sign/openpgp/PGPKeyFileUtil.java rename to src/main/java/org/simplify4u/plugins/sign/utils/FileUtil.java index b2895f4..17aa41e 100644 --- a/src/main/java/org/simplify4u/plugins/sign/openpgp/PGPKeyFileUtil.java +++ b/src/main/java/org/simplify4u/plugins/sign/utils/FileUtil.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.simplify4u.plugins.sign.openpgp; +package org.simplify4u.plugins.sign.utils; import java.io.File; @@ -23,7 +23,7 @@ * Utility to manipulate key file name. */ @UtilityClass -class PGPKeyFileUtil { +public class FileUtil { private static final String USER_HOME_PREFIX = "~" + File.separator; @@ -34,7 +34,7 @@ class PGPKeyFileUtil { * * @return file name with user hom directory */ - File calculateWithUserHome(File keyFile) { + public File calculateWithUserHome(File keyFile) { String filePath = keyFile.getPath(); diff --git a/src/test/java/org/simplify4u/plugins/sign/ArtifactSignerFactoryTest.java b/src/test/java/org/simplify4u/plugins/sign/ArtifactSignerFactoryTest.java index 2d8fa3c..604d5ba 100644 --- a/src/test/java/org/simplify4u/plugins/sign/ArtifactSignerFactoryTest.java +++ b/src/test/java/org/simplify4u/plugins/sign/ArtifactSignerFactoryTest.java @@ -31,8 +31,7 @@ @ExtendWith(MockitoExtension.class) class ArtifactSignerFactoryTest { - @Mock - private PGPKeyInfo keyInfo; + private PGPKeyInfo keyInfo = PGPKeyInfo.builder().build(); @Mock private ArtifactSigner36 artifactSigner36; diff --git a/src/test/java/org/simplify4u/plugins/sign/KeyInfoFactoryTest.java b/src/test/java/org/simplify4u/plugins/sign/KeyInfoFactoryTest.java new file mode 100644 index 0000000..a91aa19 --- /dev/null +++ b/src/test/java/org/simplify4u/plugins/sign/KeyInfoFactoryTest.java @@ -0,0 +1,160 @@ +/* + * Copyright 2021 Slawomir Jaranowski + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.simplify4u.plugins.sign; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.simplify4u.plugins.sign.KeyInfoFactory.KeyInfoRequest; +import org.simplify4u.plugins.sign.openpgp.PGPKeyInfo; +import org.simplify4u.plugins.sign.utils.Environment; +import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher; +import org.sonatype.plexus.components.sec.dispatcher.SecDispatcherException; + +@ExtendWith(MockitoExtension.class) +class KeyInfoFactoryTest { + + private static final String KEY_ID_STR = "ABCDEF0123456789"; + private static final long KEY_ID = 0xABCDEF0123456789L; + + private static final String KEY_PASS_STR = "pass"; + private static final char[] KEY_PASS = KEY_PASS_STR.toCharArray(); + + private static final File KEY_FILE = new File(PGPKeyInfo.class.getResource("/priv-key-no-pass.asc").getFile()); + + @Mock + private Environment environment; + + @Mock + private SecDispatcher secDispatcher; + + @InjectMocks + private KeyInfoFactory keyInfoFactory; + + @BeforeEach + void setup() throws SecDispatcherException { + lenient().doAnswer(invocation -> invocation.getArgument(0)) + .when(secDispatcher).decrypt(Mockito.anyString()); + } + + @Test + void keyFromFileAllPropertiesSet() throws IOException { + + // given + KeyInfoRequest keyInfoRequest = KeyInfoRequest.builder() + .id(KEY_ID_STR) + .pass(KEY_PASS_STR) + .file(KEY_FILE) + .build(); + + PGPKeyInfo keyInfo = keyInfoFactory.buildKeyInfo(keyInfoRequest); + + // then + assertThat(keyInfo.getId()).isEqualTo(KEY_ID); + assertThat(keyInfo.getPass()).isEqualTo(KEY_PASS); + assertThat(keyInfo.getKey()).containsExactly(Files.readAllBytes(KEY_FILE.toPath())); + assertThat(keyInfo.isKeyAvailable()).isTrue(); + } + + @Test + void keyFromFile() throws IOException { + + KeyInfoRequest keyInfoRequest = KeyInfoRequest.builder() + .file(KEY_FILE) + .build(); + + // when + PGPKeyInfo keyInfo = keyInfoFactory.buildKeyInfo(keyInfoRequest); + + // then + assertThat(keyInfo.getId()).isNull(); + assertThat(keyInfo.getPass()).isNull(); + assertThat(keyInfo.getKey()).containsExactly(Files.readAllBytes(KEY_FILE.toPath())); + assertThat(keyInfo.isKeyAvailable()).isTrue(); + } + + @Test + void keyDataFromEnv() { + + // given + KeyInfoRequest keyInfoRequest = KeyInfoRequest.builder() + .id("aaa") + .pass("bbb") + .file(new File("fff")) + .build(); + + mockEnvValue("SIGN_KEY", "signKey from environment"); + mockEnvValue("SIGN_KEY_ID", KEY_ID_STR); + mockEnvValue("SIGN_KEY_PASS", KEY_PASS_STR); + + + // when + PGPKeyInfo keyInfo = keyInfoFactory.buildKeyInfo(keyInfoRequest); + + // then + assertThat(keyInfo.getId()).isEqualTo(KEY_ID); + assertThat(keyInfo.getPass()).isEqualTo(KEY_PASS); + assertThat(keyInfo.getKey()).containsExactly("signKey from environment".getBytes()); + assertThat(keyInfo.isKeyAvailable()).isTrue(); + } + + + @Test + void invalidKeyIdThrowException() { + + KeyInfoRequest keyInfoRequest = KeyInfoRequest.builder() + .id("xxx") + .build(); + + assertThatThrownBy(() -> keyInfoFactory.buildKeyInfo(keyInfoRequest)) + .isExactlyInstanceOf(SignMojoException.class) + .hasMessageStartingWith("Invalid keyId: For input string: \"xxx\"") + .hasNoCause(); + } + + @Test + void notExistingKeyShouldReturnEmptyKey() { + + KeyInfoRequest keyInfoRequest = KeyInfoRequest.builder() + .file(new File("xxx/xxx.asc")) + .build(); + + // when + PGPKeyInfo keyInfo = keyInfoFactory.buildKeyInfo(keyInfoRequest); + + // then + assertThat(keyInfo.getKey()).isEmpty(); + assertThat(keyInfo.isKeyAvailable()).isFalse(); + } + + private void mockEnvValue(String key, String value) { + when(environment.getEnv(key)).thenReturn(Optional.of(value)); + } +} diff --git a/src/test/java/org/simplify4u/plugins/sign/SignMojoTest.java b/src/test/java/org/simplify4u/plugins/sign/SignMojoTest.java index 2977632..c13648b 100644 --- a/src/test/java/org/simplify4u/plugins/sign/SignMojoTest.java +++ b/src/test/java/org/simplify4u/plugins/sign/SignMojoTest.java @@ -15,10 +15,11 @@ */ package org.simplify4u.plugins.sign; -import java.io.File; +import java.util.Collections; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; @@ -26,15 +27,13 @@ import org.apache.maven.artifact.DefaultArtifact; import org.apache.maven.artifact.handler.DefaultArtifactHandler; import org.apache.maven.project.MavenProject; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; +import org.apache.maven.project.MavenProjectHelper; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.simplify4u.plugins.sign.openpgp.PGPSignerKeyNotFoundException; -import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher; +import org.simplify4u.plugins.sign.openpgp.PGPKeyInfo; @ExtendWith(MockitoExtension.class) class SignMojoTest { @@ -42,6 +41,9 @@ class SignMojoTest { @Mock private MavenProject project; + @Mock + private MavenProjectHelper projectHelper; + @Mock private ArtifactSigner artifactSigner; @@ -49,7 +51,7 @@ class SignMojoTest { private ArtifactSignerFactory artifactSignerFactory; @Mock - private SecDispatcher secDispatcher; + private KeyInfoFactory keyInfoFactory; @InjectMocks private SignMojo mojo; @@ -64,72 +66,58 @@ void skipExecution() { mojo.execute(); // then - verifyNoInteractions(artifactSignerFactory, artifactSigner, secDispatcher, project); + verifyNoInteractions(artifactSignerFactory, artifactSigner, keyInfoFactory, project); } @Test - void noExistingKeyShouldSkipExecution() { + void emptyKeyInfoShouldSkipExecution() { // given mojo.setSkipNoKey(true); - mojo.setKeyFile(new File("no-existing-key.asc")); + when(keyInfoFactory.buildKeyInfo(any())).thenReturn(PGPKeyInfo.builder().build()); // when mojo.execute(); //then - verifyNoInteractions(artifactSignerFactory, artifactSigner, secDispatcher, project); + verifyNoInteractions(artifactSignerFactory, artifactSigner, project); } @Test - void noExistingKeyShouldBreakExecution() { + void emptyKeyInfoShouldBreakExecution() { // given mojo.setSkipNoKey(false); - mojo.setKeyFile(new File("no-existing-key.asc")); + when(keyInfoFactory.buildKeyInfo(any())).thenReturn(PGPKeyInfo.builder().build()); // when - then assertThatThrownBy(() -> mojo.execute()) - .isExactlyInstanceOf(PGPSignerKeyNotFoundException.class) - .hasMessage("key file: no-existing-key.asc not found"); + .isExactlyInstanceOf(SignMojoException.class) + .hasMessage("Required key for signing not found"); - verifyNoInteractions(artifactSignerFactory, artifactSigner, secDispatcher, project); + verifyNoInteractions(artifactSignerFactory, artifactSigner, project); } - @Nested - class ExecutionTesting { - - @BeforeEach - void setup() { - DefaultArtifact artifact = new DefaultArtifact("groupId", "artifactId", "1.0.0", null, "pom", null, - new DefaultArtifactHandler("pom")); - when(project.getGroupId()).thenReturn(artifact.getGroupId()); - when(project.getArtifactId()).thenReturn(artifact.getArtifactId()); - when(project.getVersion()).thenReturn(artifact.getVersion()); - when(project.getArtifact()).thenReturn(artifact); - - when(artifactSignerFactory.getSigner(any())).thenReturn(artifactSigner); - - //setup default values of mojo - mojo.setKeyFile(new File(getClass().getResource("/priv-key-no-pass.asc").getFile())); - } + @Test + void standardFlow() { - @Test - void executeWithOutParams() { + DefaultArtifact artifact = new DefaultArtifact("groupId", "artifactId", "1.0.0", null, "pom", null, + new DefaultArtifactHandler("pom")); + when(project.getGroupId()).thenReturn(artifact.getGroupId()); + when(project.getArtifactId()).thenReturn(artifact.getArtifactId()); + when(project.getVersion()).thenReturn(artifact.getVersion()); + when(project.getArtifact()).thenReturn(artifact); - mojo.execute(); + when(keyInfoFactory.buildKeyInfo(any())).thenReturn(PGPKeyInfo.builder().key(new byte[]{1, 2, 3}).build()); - verify(artifactSigner).signArtifact(any()); - verifyNoInteractions(secDispatcher); - } + when(artifactSignerFactory.getSigner(any())).thenReturn(artifactSigner); - @Test - void executeWithPassword() throws Exception { + when(artifactSigner.signArtifact(any())).thenReturn(Collections.singletonList(SignResult.builder().build())); - mojo.setKeyPass("keyPass"); - mojo.execute(); + mojo.execute(); - verify(artifactSigner).signArtifact(any()); - verify(secDispatcher).decrypt("keyPass"); - } + verify(artifactSigner).signArtifact(any()); + verify(projectHelper).attachArtifact(eq(project), any(), any(), any()); } } + + diff --git a/src/test/java/org/simplify4u/plugins/sign/openpgp/PGPKeyInfoTest.java b/src/test/java/org/simplify4u/plugins/sign/openpgp/PGPKeyInfoTest.java deleted file mode 100644 index b1aebb5..0000000 --- a/src/test/java/org/simplify4u/plugins/sign/openpgp/PGPKeyInfoTest.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2020 Slawomir Jaranowski - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.simplify4u.plugins.sign.openpgp; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junitpioneer.jupiter.SetEnvironmentVariable; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.slf4j.Logger; - -@ExtendWith(MockitoExtension.class) -class PGPKeyInfoTest { - - private static final String KEY_ID_STR = "ABCDEF0123456789"; - private static final long KEY_ID = 0xABCDEF0123456789L; - - private static final String KEY_PASS_STR = "pass"; - private static final char[] KEY_PASS = KEY_PASS_STR.toCharArray(); - - private static final File KEY_FILE = new File(PGPKeyInfo.class.getResource("/priv-key-no-pass.asc").getFile()); - - @Mock(name = "org.simplify4u.plugins.sign.openpgp.PGPKeyInfo") - Logger logger; - - @Test - void keyFromFileAllPropertiesSet() throws IOException { - // when - PGPKeyInfo keyInfo = PGPKeyInfo.builder() - .keyId(KEY_ID_STR) - .keyPass(KEY_PASS_STR) - .keyFile(KEY_FILE) - .build(); - - // then - assertThat(keyInfo.getId()).isEqualTo(KEY_ID); - assertThat(keyInfo.getPass()).isEqualTo(KEY_PASS); - assertThat(keyInfo.getKey()).hasSameContentAs(Files.newInputStream(KEY_FILE.toPath())); - verify(logger).debug("No {} set as environment variable", "SIGN_KEY_ID"); - verify(logger).debug("No {} set as environment variable", "SIGN_KEY_PASS"); - verify(logger).debug("No {} set as environment variable", "SIGN_KEY"); - verify(logger).debug(eq("Read key from file: {}"), any(File.class)); - verifyNoMoreInteractions(logger); - } - - - @Test - void keyFromFile() throws IOException { - - // when - PGPKeyInfo keyInfo = PGPKeyInfo.builder() - .keyFile(KEY_FILE) - .build(); - - // then - assertThat(keyInfo.getId()).isNull(); - assertThat(keyInfo.getPass()).isNull(); - assertThat(keyInfo.getKey()).hasSameContentAs(Files.newInputStream(KEY_FILE.toPath())); - } - - @Test - @SetEnvironmentVariable(key = "SIGN_KEY", value = "signKey from environment") - @SetEnvironmentVariable(key = "SIGN_KEY_ID", value = KEY_ID_STR) - @SetEnvironmentVariable(key = "SIGN_KEY_PASS", value = KEY_PASS_STR) - void keyDataFromEnv() { - - // when - PGPKeyInfo keyInfo = PGPKeyInfo.builder() - .keyId("aaa") - .keyPass("bbb") - .keyFile(new File("fff")) - .build(); - - // then - assertThat(keyInfo.getId()).isEqualTo(KEY_ID); - assertThat(keyInfo.getPass()).isEqualTo(KEY_PASS); - assertThat(keyInfo.getKey()).hasContent("signKey from environment"); - verify(logger).debug("Retrieved {} configuration from environment variable", "SIGN_KEY_ID"); - verify(logger).debug("Retrieved {} configuration from environment variable", "SIGN_KEY_PASS"); - verify(logger).debug("Retrieved {} configuration from environment variable", "SIGN_KEY"); - verifyNoMoreInteractions(logger); - } - - @Test - @SetEnvironmentVariable(key = "SIGN_KEY", value = "") - @SetEnvironmentVariable(key = "SIGN_KEY_ID", value = "") - @SetEnvironmentVariable(key = "SIGN_KEY_PASS", value = "") - void allowEmptyValueInEnvVariable() throws IOException { - - // when - PGPKeyInfo keyInfo = PGPKeyInfo.builder() - .keyId(KEY_ID_STR) - .keyPass(KEY_PASS_STR) - .keyFile(KEY_FILE) - .build(); - - // then - assertThat(keyInfo.getId()).isEqualTo(KEY_ID); - assertThat(keyInfo.getPass()).isEqualTo(KEY_PASS); - assertThat(keyInfo.getKey()).hasSameContentAs(Files.newInputStream(KEY_FILE.toPath())); - - verify(logger).debug("No {} set as environment variable", "SIGN_KEY_ID"); - verify(logger).debug("No {} set as environment variable", "SIGN_KEY_PASS"); - verify(logger).debug("No {} set as environment variable", "SIGN_KEY"); - verify(logger).debug(eq("Read key from file: {}"), any(File.class)); - verifyNoMoreInteractions(logger); - } - - @Test - @SetEnvironmentVariable(key = "SIGN_KEY", value = "signKey from environment") - void keyFromEnvWithFile() { - - // when - PGPKeyInfo keyInfo = PGPKeyInfo.builder() - .keyFile(KEY_FILE) - .build(); - - // then - assertThat(keyInfo.getId()).isNull(); - assertThat(keyInfo.getPass()).isNull(); - assertThat(keyInfo.getKey()).hasContent("signKey from environment"); - } - - @Test - void invalidKeyIdThrowException() { - - PGPKeyInfo.PGPKeyInfoBuilder keyInfoBuilder = PGPKeyInfo.builder() - .keyId("xxx"); - - assertThatThrownBy(keyInfoBuilder::build) - .isExactlyInstanceOf(PGPSignerException.class) - .hasMessageStartingWith("Invalid keyId: For input string: \"xxx\"") - .hasNoCause(); - } - - @Test - @SetEnvironmentVariable(key = "SIGN_KEY", value = "signKey from environment") - @SetEnvironmentVariable(key = "SIGN_KEY_ID", value = "null") - void nullStringInEnvironmentValueShouldBeFiltered() { - // when - PGPKeyInfo keyInfo = PGPKeyInfo.builder() - .build(); - - // then - assertThat(keyInfo.getId()).isNull(); - assertThat(keyInfo.getPass()).isNull(); - assertThat(keyInfo.getKey()).hasContent("signKey from environment"); - } - - @Test - void passDecryptorShouldBeCalled() { - - // when - PGPKeyInfo keyInfo = PGPKeyInfo.builder() - .passDecryptor(String::toUpperCase) - .keyPass(KEY_PASS_STR) - .keyFile(KEY_FILE) - .build(); - - // then - assertThat(keyInfo.getPass()).isEqualTo(KEY_PASS_STR.toUpperCase().toCharArray()); - } -} diff --git a/src/test/java/org/simplify4u/plugins/sign/openpgp/PGPSignerTest.java b/src/test/java/org/simplify4u/plugins/sign/openpgp/PGPSignerTest.java index 41cbf92..59c6f22 100644 --- a/src/test/java/org/simplify4u/plugins/sign/openpgp/PGPSignerTest.java +++ b/src/test/java/org/simplify4u/plugins/sign/openpgp/PGPSignerTest.java @@ -16,6 +16,8 @@ package org.simplify4u.plugins.sign.openpgp; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZoneOffset; @@ -24,6 +26,7 @@ import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import io.vavr.control.Try; import org.bouncycastle.openpgp.PGPException; import org.junit.jupiter.api.Test; @@ -32,16 +35,15 @@ class PGPSignerTest { private PGPSigner pgpSigner = new PGPSigner(); @Test - void loadKeyWithAllProperties() throws PGPSignerException { + void loadKeyWithAllProperties() throws PGPSignerException, IOException { // given PGPKeyInfo keyInfo = PGPKeyInfo.builder() - .keyId("AC71B3E31C0C0D38") - .keyPass("testPass") - .keyFile(new File(getClass().getResource("/priv-key.asc").getFile())) + .id(0xAC71B3E31C0C0D38L) + .pass("testPass") + .key(byteFromResource("/priv-key.asc")) .build(); - // when assertThatCode(() -> pgpSigner.setKeyInfo(keyInfo)) .doesNotThrowAnyException(); @@ -52,11 +54,10 @@ void loadDefaultKey() throws PGPSignerException { // given PGPKeyInfo keyInfo = PGPKeyInfo.builder() - .keyPass("testPass") - .keyFile(new File(getClass().getResource("/priv-key.asc").getFile())) + .pass("testPass") + .key(byteFromResource("/priv-key.asc")) .build(); - // when assertThatCode(() -> pgpSigner.setKeyInfo(keyInfo)) .doesNotThrowAnyException(); @@ -67,21 +68,35 @@ void loadDefaultKeyWithOutPass() throws PGPSignerException { // given PGPKeyInfo keyInfo = PGPKeyInfo.builder() - .keyFile(new File(getClass().getResource("/priv-key-no-pass.asc").getFile())) + .key(byteFromResource("/priv-key-no-pass.asc")) .build(); - // when assertThatCode(() -> pgpSigner.setKeyInfo(keyInfo)) .doesNotThrowAnyException(); } + @Test + void notFoundKeyThrowException() throws PGPSignerException { + + // given + PGPKeyInfo keyInfo = PGPKeyInfo.builder() + .id(0x1234567890L) + .key(byteFromResource("/priv-key-no-pass.asc")) + .build(); + + // when + assertThatCode(() -> pgpSigner.setKeyInfo(keyInfo)) + .isExactlyInstanceOf(PGPSignerException.class) + .hasMessage("Secret key not found"); + } + @Test void loadSubKeyWithOutPass() throws PGPSignerException { // given PGPKeyInfo keyInfo = PGPKeyInfo.builder() - .keyFile(new File(getClass().getResource("/priv-sub-key-no-pass.asc").getFile())) + .key(byteFromResource("/priv-sub-key-no-pass.asc")) .build(); @@ -95,11 +110,10 @@ void loadSubKeyWithMasterKey() throws PGPSignerException { // given PGPKeyInfo keyInfo = PGPKeyInfo.builder() - .keyId("0C5CEA1C96038404") - .keyFile(new File(getClass().getResource("/priv-sub-key-no-pass.asc").getFile())) + .id(0x0C5CEA1C96038404L) + .key(byteFromResource("/priv-sub-key-no-pass.asc")) .build(); - // when assertThatThrownBy(() -> pgpSigner.setKeyInfo(keyInfo)) .isExactlyInstanceOf(PGPSignerException.class) @@ -111,11 +125,10 @@ void keyWithOutPassButPassGiven() throws PGPSignerException { // given PGPKeyInfo keyInfo = PGPKeyInfo.builder() - .keyPass("testPass") - .keyFile(new File(getClass().getResource("/priv-key-no-pass.asc").getFile())) + .pass("testPass") + .key(byteFromResource("/priv-key-no-pass.asc")) .build(); - // when assertThatCode(() -> pgpSigner.setKeyInfo(keyInfo)) .doesNotThrowAnyException(); @@ -126,10 +139,9 @@ void requireNullPassThrowException() throws PGPSignerException { // given PGPKeyInfo keyInfo = PGPKeyInfo.builder() - .keyFile(new File(getClass().getResource("/priv-key.asc").getFile())) + .key(byteFromResource("/priv-key.asc")) .build(); - // when assertThatThrownBy(() -> pgpSigner.setKeyInfo(keyInfo)) .isExactlyInstanceOf(PGPSignerException.class) @@ -142,11 +154,10 @@ void requireInvalidPassThrowException() throws PGPSignerException { // given PGPKeyInfo keyInfo = PGPKeyInfo.builder() - .keyPass("xxx") - .keyFile(new File(getClass().getResource("/priv-key.asc").getFile())) + .pass("xxx") + .key(byteFromResource("/priv-key.asc")) .build(); - // when assertThatThrownBy(() -> pgpSigner.setKeyInfo(keyInfo)) .isExactlyInstanceOf(PGPSignerException.class) @@ -164,8 +175,8 @@ void expiredMasterKeyThrewException() { .toLocalDateTime(); PGPKeyInfo keyInfo = PGPKeyInfo.builder() - .keyId("B09391374A115DE2") - .keyFile(new File(getClass().getResource("/priv-expired-key-no-pass.asc").getFile())) + .id(0xB09391374A115DE2L) + .key(byteFromResource("/priv-expired-key-no-pass.asc")) .build(); assertThatThrownBy(() -> pgpSigner.setKeyInfo(keyInfo)) @@ -184,8 +195,8 @@ void expiredSubKeyThrewException() { .toLocalDateTime(); PGPKeyInfo keyInfo = PGPKeyInfo.builder() - .keyId("44A920F7DCC8A31E") - .keyFile(new File(getClass().getResource("/priv-expired-key-no-pass.asc").getFile())) + .id(0x44A920F7DCC8A31EL) + .key(byteFromResource("/priv-expired-key-no-pass.asc")) .build(); assertThatThrownBy(() -> pgpSigner.setKeyInfo(keyInfo)) @@ -195,4 +206,7 @@ void expiredSubKeyThrewException() { + expiredDateTime); } + private byte[] byteFromResource(String name) { + return Try.of(() -> Files.readAllBytes(new File(getClass().getResource(name).getFile()).toPath())).get(); + } } diff --git a/src/test/java/org/simplify4u/plugins/sign/utils/EnvironmentTest.java b/src/test/java/org/simplify4u/plugins/sign/utils/EnvironmentTest.java new file mode 100644 index 0000000..78d3273 --- /dev/null +++ b/src/test/java/org/simplify4u/plugins/sign/utils/EnvironmentTest.java @@ -0,0 +1,101 @@ +/* + * Copyright 2021 Slawomir Jaranowski + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.simplify4u.plugins.sign.utils; + +import java.util.Optional; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.slf4j.Logger; + +@ExtendWith(MockitoExtension.class) +class EnvironmentTest { + + public static final String TEST_ENV_NAME = "TEST"; + + @Mock + private UnaryOperator envGetter; + + @Mock + private Logger logger; + + @InjectMocks + private Environment environment; + + public static Stream shouldReadProperEnvironmentVariable() { + return Stream.of( + arguments(null, null), + arguments("null", null), + arguments("", null), + arguments(" ", null), + arguments(" trim value ", "trim value") + ); + } + + @ParameterizedTest + @MethodSource + void shouldReadProperEnvironmentVariable(String envVariableValue, String result) { + + when(envGetter.apply(TEST_ENV_NAME)).thenReturn(envVariableValue); + + Optional test = environment.getEnv(TEST_ENV_NAME); + assertThat(test).isEqualTo(Optional.ofNullable(result)); + + verify(envGetter).apply(TEST_ENV_NAME); + verifyNoMoreInteractions(envGetter); + } + + @Test + void debugLogForEmptyValue() { + + when(envGetter.apply(TEST_ENV_NAME)).thenReturn(null); + + Optional test = environment.getEnv(TEST_ENV_NAME); + + assertThat(test).isEmpty(); + + verify(logger).debug("No {} set as environment variable", TEST_ENV_NAME); + verifyNoMoreInteractions(logger); + } + + @Test + void debugLogForNotEmptyValue() { + + when(envGetter.apply(TEST_ENV_NAME)).thenReturn("Test"); + + Optional test = environment.getEnv(TEST_ENV_NAME); + + assertThat(test).hasValue("Test"); + + verify(logger).debug("Retrieved {} configuration from environment variable", TEST_ENV_NAME); + verifyNoMoreInteractions(logger); + } + +} diff --git a/src/test/java/org/simplify4u/plugins/sign/openpgp/PGPKeyFileUtilTest.java b/src/test/java/org/simplify4u/plugins/sign/utils/FileUtilTest.java similarity index 93% rename from src/test/java/org/simplify4u/plugins/sign/openpgp/PGPKeyFileUtilTest.java rename to src/test/java/org/simplify4u/plugins/sign/utils/FileUtilTest.java index a1d6cd1..3cf45fc 100644 --- a/src/test/java/org/simplify4u/plugins/sign/openpgp/PGPKeyFileUtilTest.java +++ b/src/test/java/org/simplify4u/plugins/sign/utils/FileUtilTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.simplify4u.plugins.sign.openpgp; +package org.simplify4u.plugins.sign.utils; import java.io.File; import java.util.stream.Stream; @@ -25,7 +25,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -class PGPKeyFileUtilTest { +class FileUtilTest { private static String userHome = System.getProperty("user.home"); @@ -52,7 +52,7 @@ private static File f(String parent, String fineName) { @MethodSource void shouldDetectUserHomeInPath(File in, File out) { - File file = PGPKeyFileUtil.calculateWithUserHome(in); + File file = FileUtil.calculateWithUserHome(in); assertThat(file).isEqualTo(out); } }