From 8db0301141182f1cd8dd39e26365ce3e45bd1ccd Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Fri, 31 Jul 2020 10:26:44 +0200 Subject: [PATCH] cleanup --- .../java/sonia/scm/repository/Signature.java | 2 +- .../api/RepositoryServiceFactory.java | 6 + .../java/sonia/scm/security/PublicKey.java | 4 +- .../scm/security/PublicKeyCreatedEvent.java | 35 ++ .../src/__snapshots__/storyshots.test.ts.snap | 436 +++++++++++++++++- .../src/repos/changesets/SignatureIcon.tsx | 3 +- scm-ui/ui-types/src/Changesets.ts | 2 +- .../java/sonia/scm/security/gpg/GpgKey.java | 7 +- .../scm/security/gpg/PublicKeyMapper.java | 1 + .../scm/security/gpg/PublicKeyStore.java | 17 +- .../sonia/scm/security/gpg/RawGpgKey.java | 3 +- .../scm/security/gpg/DefaultGPGTest.java | 10 +- .../gpg/PgpPublicKeyExtractorTest.java | 4 +- .../scm/security/gpg/PublicKeyMapperTest.java | 1 - .../scm/security/gpg/PublicKeyStoreTest.java | 5 + 15 files changed, 506 insertions(+), 30 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/security/PublicKeyCreatedEvent.java diff --git a/scm-core/src/main/java/sonia/scm/repository/Signature.java b/scm-core/src/main/java/sonia/scm/repository/Signature.java index 0f9a4f996a..f0f3e1e492 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Signature.java +++ b/scm-core/src/main/java/sonia/scm/repository/Signature.java @@ -44,7 +44,7 @@ public class Signature implements Serializable { private final String type; private final SignatureStatus status; private final String owner; - private final Set contacts; + private final Set contacts; public Optional getOwner() { return Optional.ofNullable(owner); diff --git a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java index fdc936af1f..e954c8088f 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java @@ -55,6 +55,7 @@ import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.spi.RepositoryServiceProvider; import sonia.scm.repository.spi.RepositoryServiceResolver; import sonia.scm.repository.work.WorkdirProvider; +import sonia.scm.security.PublicKeyCreatedEvent; import sonia.scm.security.PublicKeyDeletedEvent; import sonia.scm.security.ScmSecurityException; @@ -335,6 +336,11 @@ public final class RepositoryServiceFactory { cacheManager.getCache(LogCommandBuilder.CACHE_NAME).clear(); } + @Subscribe + public void onEvent(PublicKeyCreatedEvent event) { + cacheManager.getCache(LogCommandBuilder.CACHE_NAME).clear(); + } + @SuppressWarnings({"unchecked", "java:S3740", "rawtypes"}) private void clearCaches(final String repositoryId) { if (logger.isDebugEnabled()) { diff --git a/scm-core/src/main/java/sonia/scm/security/PublicKey.java b/scm-core/src/main/java/sonia/scm/security/PublicKey.java index bcce1814fa..003863d696 100644 --- a/scm-core/src/main/java/sonia/scm/security/PublicKey.java +++ b/scm-core/src/main/java/sonia/scm/security/PublicKey.java @@ -24,6 +24,8 @@ package sonia.scm.security; +import sonia.scm.repository.Person; + import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.Optional; @@ -62,7 +64,7 @@ public interface PublicKey { * * @return owner or empty optional */ - Set getContacts(); + Set getContacts(); /** * Verifies that the signature is valid for the given data. diff --git a/scm-core/src/main/java/sonia/scm/security/PublicKeyCreatedEvent.java b/scm-core/src/main/java/sonia/scm/security/PublicKeyCreatedEvent.java new file mode 100644 index 0000000000..a79aeca9c4 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/security/PublicKeyCreatedEvent.java @@ -0,0 +1,35 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.security; + +import sonia.scm.event.Event; + +/** + * This event is fired when a public key was created in SCM-Manager. + * @since 2.4.0 + */ +@Event +public class PublicKeyCreatedEvent { +} diff --git a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap index 69584d643e..957542c72c 100644 --- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap +++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap @@ -1716,7 +1716,7 @@ exports[`Storyshots CardColumnSmall Minimal 1`] = ` exports[`Storyshots Changesets Co-Authors with avatar 1`] = `
`; +exports[`Storyshots Changesets With invalid signature 1`] = ` +
+
+
+
+
+
+
+
+

+ + initialize repository + +

+

+

+

+

+ changeset.contributors.authoredBy + + + SCM Administrator + +

+ + + +
+
+
+
+
+
+
+
+
+

+ +

+

+ +

+
+
+
+
+
+`; + exports[`Storyshots Changesets With multiple Co-Authors 1`] = `
`; +exports[`Storyshots Changesets With unknown signature 1`] = ` +
+
+
+
+
+
+
+
+

+ + initialize repository + +

+

+

+

+

+ changeset.contributors.authoredBy + + + SCM Administrator + +

+ + + +
+
+
+
+
+
+
+
+
+

+ +

+

+ +

+
+
+
+
+
+`; + +exports[`Storyshots Changesets With valid signature 1`] = ` +
+
+
+
+
+
+
+
+

+ + initialize repository + +

+

+

+

+

+ changeset.contributors.authoredBy + + + SCM Administrator + +

+ + + +
+
+
+
+
+
+
+
+
+

+ +

+

+ +

+
+
+
+
+
+`; + exports[`Storyshots Date Date from now 1`] = `
+
  • + + profile.publicKeysNavLink + +
  • +
  • + + profile.publicKeysNavLink + +
  • +
  • + + profile.publicKeysNavLink + +
  • +
  • + + profile.publicKeysNavLink + +
  • = ({ signatures, className }) => { "changeset.signatureStatus" )}: ${status}`; + console.log(signature.contacts) if (signature.contacts?.length > 0) { message += `\n${t("changeset.keyContacts")}:`; signature.contacts.forEach((contact) => { - message += `\n- ${contact}`; + message += `\n- ${contact.name} <${contact.mail}>`; }); } return message; diff --git a/scm-ui/ui-types/src/Changesets.ts b/scm-ui/ui-types/src/Changesets.ts index 06a2a261d3..04d2913647 100644 --- a/scm-ui/ui-types/src/Changesets.ts +++ b/scm-ui/ui-types/src/Changesets.ts @@ -47,7 +47,7 @@ export type Signature = { type: string; status: "VERIFIED" | "NOT_FOUND" | "INVALID"; owner: string; - contacts: string[]; + contacts: Person[]; } export type Contributor = { diff --git a/scm-webapp/src/main/java/sonia/scm/security/gpg/GpgKey.java b/scm-webapp/src/main/java/sonia/scm/security/gpg/GpgKey.java index e2552e1e39..cf1aeb5a3f 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/gpg/GpgKey.java +++ b/scm-webapp/src/main/java/sonia/scm/security/gpg/GpgKey.java @@ -36,6 +36,7 @@ import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.repository.Person; import sonia.scm.security.PublicKey; import java.io.ByteArrayInputStream; @@ -52,9 +53,9 @@ public class GpgKey implements PublicKey { private final String id; private final String owner; private final String raw; - private final Set contacts; + private final Set contacts; - public GpgKey(String id, String owner, String raw, Set contacts) { + public GpgKey(String id, String owner, String raw, Set contacts) { this.id = id; this.owner = owner; this.raw = raw; @@ -80,7 +81,7 @@ public class GpgKey implements PublicKey { } @Override - public Set getContacts() { + public Set getContacts() { return contacts; } diff --git a/scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyMapper.java b/scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyMapper.java index fbd6a5cff9..7e5ef8ce8c 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyMapper.java @@ -51,6 +51,7 @@ public abstract class PublicKeyMapper { } @Mapping(target = "attributes", ignore = true) + @Mapping(target = "raw", ignore = true) abstract RawGpgKeyDto map(RawGpgKey rawGpgKey); @ObjectFactory diff --git a/scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyStore.java b/scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyStore.java index cb606af1ca..31586850b8 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyStore.java +++ b/scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyStore.java @@ -25,11 +25,11 @@ package sonia.scm.security.gpg; import org.bouncycastle.openpgp.PGPPublicKey; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import sonia.scm.ContextEntry; import sonia.scm.event.ScmEventBus; +import sonia.scm.repository.Person; import sonia.scm.security.NotPublicKeyException; +import sonia.scm.security.PublicKeyCreatedEvent; import sonia.scm.security.PublicKeyDeletedEvent; import sonia.scm.store.DataStore; import sonia.scm.store.DataStoreFactory; @@ -38,6 +38,7 @@ import sonia.scm.user.UserPermissions; import javax.inject.Inject; import javax.inject.Singleton; import java.time.Instant; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -80,22 +81,24 @@ public class PublicKeyStore { RawGpgKey key = new RawGpgKey(master, displayName, username, rawKey, getContactsFromPublicKey(rawKey), Instant.now()); store.put(master, key); + eventBus.post(new PublicKeyCreatedEvent()); return key; } - private Set getContactsFromPublicKey(String rawKey) { - Set contacts = new HashSet<>(); + private Set getContactsFromPublicKey(String rawKey) { + List userIds = new ArrayList<>(); Optional publicKeyFromRawKey = getFromRawKey(rawKey); - publicKeyFromRawKey.ifPresent(pgpPublicKey -> pgpPublicKey.getUserIDs().forEachRemaining(contacts::add)); - return contacts; + publicKeyFromRawKey.ifPresent(pgpPublicKey -> pgpPublicKey.getUserIDs().forEachRemaining(userIds::add)); + + return userIds.stream().map(Person::toPerson).collect(Collectors.toSet()); } public void delete(String id) { RawGpgKey rawGpgKey = store.get(id); if (rawGpgKey != null) { - UserPermissions.modify(rawGpgKey.getOwner()).check(); + UserPermissions.changePublicKeys(rawGpgKey.getOwner()).check(); store.remove(id); eventBus.post(new PublicKeyDeletedEvent()); } diff --git a/scm-webapp/src/main/java/sonia/scm/security/gpg/RawGpgKey.java b/scm-webapp/src/main/java/sonia/scm/security/gpg/RawGpgKey.java index 19c88296c8..eaf099e68f 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/gpg/RawGpgKey.java +++ b/scm-webapp/src/main/java/sonia/scm/security/gpg/RawGpgKey.java @@ -28,6 +28,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import sonia.scm.repository.Person; import sonia.scm.xml.XmlInstantAdapter; import javax.xml.bind.annotation.XmlAccessType; @@ -49,7 +50,7 @@ public class RawGpgKey { private String displayName; private String owner; private String raw; - private Set contacts; + private Set contacts; @XmlJavaTypeAdapter(XmlInstantAdapter.class) private Instant created; diff --git a/scm-webapp/src/test/java/sonia/scm/security/gpg/DefaultGPGTest.java b/scm-webapp/src/test/java/sonia/scm/security/gpg/DefaultGPGTest.java index a3b4b46582..924d18e859 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/gpg/DefaultGPGTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/gpg/DefaultGPGTest.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.repository.Person; import sonia.scm.security.PublicKey; import java.io.IOException; @@ -52,16 +53,17 @@ class DefaultGPGTest { @Test void shouldFindIdInSignature() throws IOException { - String raw = GPGTestHelper.readResourceAsString("signature.asc"); + String raw = GPGTestHelper.readResourceAsString("slarti.txt.asc"); String publicKeyId = gpg.findPublicKeyId(raw.getBytes()); - assertThat(publicKeyId).isEqualTo("0x1F17B79A09DAD5B9"); + assertThat(publicKeyId).isEqualTo("0x247E908C6FD35473"); } @Test void shouldFindPublicKey() throws IOException { String raw = GPGTestHelper.readResourceAsString("subkeys.asc"); - RawGpgKey key1 = new RawGpgKey("42", "key_42", "trillian", raw, ImmutableSet.of("trillian", "zaphod"), Instant.now()); + Person trillian = Person.toPerson("Trillian "); + RawGpgKey key1 = new RawGpgKey("42", "key_42", "trillian", raw, ImmutableSet.of(trillian), Instant.now()); when(store.findById("42")).thenReturn(Optional.of(key1)); @@ -71,7 +73,7 @@ class DefaultGPGTest { assertThat(publicKey.get().getOwner()).isPresent(); assertThat(publicKey.get().getOwner().get()).contains("trillian"); assertThat(publicKey.get().getId()).isEqualTo("42"); - assertThat(publicKey.get().getContacts()).contains("trillian", "zaphod"); + assertThat(publicKey.get().getContacts()).contains(trillian); } @Test diff --git a/scm-webapp/src/test/java/sonia/scm/security/gpg/PgpPublicKeyExtractorTest.java b/scm-webapp/src/test/java/sonia/scm/security/gpg/PgpPublicKeyExtractorTest.java index ca50d58367..ebb328351b 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/gpg/PgpPublicKeyExtractorTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/gpg/PgpPublicKeyExtractorTest.java @@ -36,12 +36,12 @@ class PgpPublicKeyExtractorTest { @Test void shouldExtractPublicKeyFromRawKey() throws IOException { - String raw = GPGTestHelper.readResourceAsString("pubKeyEH.asc"); + String raw = GPGTestHelper.readResourceAsString("single.asc"); Optional publicKey = PgpPublicKeyExtractor.getFromRawKey(raw); assertThat(publicKey).isPresent(); - assertThat(Long.toHexString(publicKey.get().getKeyID())).isEqualTo("39ad4bed55527f1c"); + assertThat(Long.toHexString(publicKey.get().getKeyID())).isEqualTo("975922f193b07d6e"); } } diff --git a/scm-webapp/src/test/java/sonia/scm/security/gpg/PublicKeyMapperTest.java b/scm-webapp/src/test/java/sonia/scm/security/gpg/PublicKeyMapperTest.java index 13f299e826..764e037eea 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/gpg/PublicKeyMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/gpg/PublicKeyMapperTest.java @@ -75,7 +75,6 @@ class PublicKeyMapperTest { RawGpgKeyDto dto = mapper.map(key); assertThat(dto.getDisplayName()).isEqualTo(key.getDisplayName()); - assertThat(dto.getRaw()).isEqualTo(key.getRaw()); assertThat(dto.getCreated()).isEqualTo(key.getCreated()); assertThat(dto.getLinks().getLinkBy("self").get().getHref()).isEqualTo("/v2/public_keys/1"); assertThat(dto.getLinks().getLinkBy("delete").get().getHref()).isEqualTo("/v2/public_keys/delete/1"); diff --git a/scm-webapp/src/test/java/sonia/scm/security/gpg/PublicKeyStoreTest.java b/scm-webapp/src/test/java/sonia/scm/security/gpg/PublicKeyStoreTest.java index d44f8e2cf7..93e999c426 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/gpg/PublicKeyStoreTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/gpg/PublicKeyStoreTest.java @@ -34,7 +34,9 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import sonia.scm.event.ScmEventBus; +import sonia.scm.repository.Person; import sonia.scm.security.NotPublicKeyException; +import sonia.scm.security.PublicKeyCreatedEvent; import sonia.scm.security.PublicKeyDeletedEvent; import sonia.scm.store.DataStoreFactory; import sonia.scm.store.InMemoryDataStoreFactory; @@ -103,6 +105,9 @@ class PublicKeyStoreTest { assertThat(key.getOwner()).isEqualTo("trillian"); assertThat(key.getCreated()).isAfterOrEqualTo(now); assertThat(key.getRaw()).isEqualTo(rawKey); + assertThat(key.getContacts()).contains(Person.toPerson("SCM Packages (signing key for packages.scm-manager.org) ")); + + verify(eventBus).post(any(PublicKeyCreatedEvent.class)); } @Test