This commit is contained in:
Eduard Heimbuch
2020-07-31 10:26:44 +02:00
parent f4ab367220
commit 8db0301141
15 changed files with 506 additions and 30 deletions

View File

@@ -44,7 +44,7 @@ public class Signature implements Serializable {
private final String type; private final String type;
private final SignatureStatus status; private final SignatureStatus status;
private final String owner; private final String owner;
private final Set<String> contacts; private final Set<Person> contacts;
public Optional<String> getOwner() { public Optional<String> getOwner() {
return Optional.ofNullable(owner); return Optional.ofNullable(owner);

View File

@@ -55,6 +55,7 @@ import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.repository.spi.RepositoryServiceProvider; import sonia.scm.repository.spi.RepositoryServiceProvider;
import sonia.scm.repository.spi.RepositoryServiceResolver; import sonia.scm.repository.spi.RepositoryServiceResolver;
import sonia.scm.repository.work.WorkdirProvider; import sonia.scm.repository.work.WorkdirProvider;
import sonia.scm.security.PublicKeyCreatedEvent;
import sonia.scm.security.PublicKeyDeletedEvent; import sonia.scm.security.PublicKeyDeletedEvent;
import sonia.scm.security.ScmSecurityException; import sonia.scm.security.ScmSecurityException;
@@ -335,6 +336,11 @@ public final class RepositoryServiceFactory {
cacheManager.getCache(LogCommandBuilder.CACHE_NAME).clear(); cacheManager.getCache(LogCommandBuilder.CACHE_NAME).clear();
} }
@Subscribe
public void onEvent(PublicKeyCreatedEvent event) {
cacheManager.getCache(LogCommandBuilder.CACHE_NAME).clear();
}
@SuppressWarnings({"unchecked", "java:S3740", "rawtypes"}) @SuppressWarnings({"unchecked", "java:S3740", "rawtypes"})
private void clearCaches(final String repositoryId) { private void clearCaches(final String repositoryId) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {

View File

@@ -24,6 +24,8 @@
package sonia.scm.security; package sonia.scm.security;
import sonia.scm.repository.Person;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.util.Optional; import java.util.Optional;
@@ -62,7 +64,7 @@ public interface PublicKey {
* *
* @return owner or empty optional * @return owner or empty optional
*/ */
Set<String> getContacts(); Set<Person> getContacts();
/** /**
* Verifies that the signature is valid for the given data. * Verifies that the signature is valid for the given data.

View File

@@ -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 {
}

View File

@@ -1716,7 +1716,7 @@ exports[`Storyshots CardColumnSmall Minimal 1`] = `
exports[`Storyshots Changesets Co-Authors with avatar 1`] = ` exports[`Storyshots Changesets Co-Authors with avatar 1`] = `
<div <div
className="Changesetsstories__Wrapper-sc-122npan-0 hHKBXk box box-link-shadow" className="Changesetsstories__Wrapper-sc-122npan-0 gDnzNS box box-link-shadow"
> >
<div <div
className="ChangesetRow__Wrapper-tkpti5-0 bRWdJS" className="ChangesetRow__Wrapper-tkpti5-0 bRWdJS"
@@ -1886,7 +1886,7 @@ exports[`Storyshots Changesets Co-Authors with avatar 1`] = `
exports[`Storyshots Changesets Commiter and Co-Authors with avatar 1`] = ` exports[`Storyshots Changesets Commiter and Co-Authors with avatar 1`] = `
<div <div
className="Changesetsstories__Wrapper-sc-122npan-0 hHKBXk box box-link-shadow" className="Changesetsstories__Wrapper-sc-122npan-0 gDnzNS box box-link-shadow"
> >
<div <div
className="ChangesetRow__Wrapper-tkpti5-0 bRWdJS" className="ChangesetRow__Wrapper-tkpti5-0 bRWdJS"
@@ -2045,7 +2045,7 @@ exports[`Storyshots Changesets Commiter and Co-Authors with avatar 1`] = `
exports[`Storyshots Changesets Default 1`] = ` exports[`Storyshots Changesets Default 1`] = `
<div <div
className="Changesetsstories__Wrapper-sc-122npan-0 hHKBXk box box-link-shadow" className="Changesetsstories__Wrapper-sc-122npan-0 gDnzNS box box-link-shadow"
> >
<div <div
className="ChangesetRow__Wrapper-tkpti5-0 bRWdJS" className="ChangesetRow__Wrapper-tkpti5-0 bRWdJS"
@@ -2162,7 +2162,7 @@ exports[`Storyshots Changesets Default 1`] = `
exports[`Storyshots Changesets Replacements 1`] = ` exports[`Storyshots Changesets Replacements 1`] = `
<div <div
className="Changesetsstories__Wrapper-sc-122npan-0 hHKBXk box box-link-shadow" className="Changesetsstories__Wrapper-sc-122npan-0 gDnzNS box box-link-shadow"
> >
<div <div
className="ChangesetRow__Wrapper-tkpti5-0 bRWdJS" className="ChangesetRow__Wrapper-tkpti5-0 bRWdJS"
@@ -2289,7 +2289,7 @@ exports[`Storyshots Changesets Replacements 1`] = `
exports[`Storyshots Changesets With Committer 1`] = ` exports[`Storyshots Changesets With Committer 1`] = `
<div <div
className="Changesetsstories__Wrapper-sc-122npan-0 hHKBXk box box-link-shadow" className="Changesetsstories__Wrapper-sc-122npan-0 gDnzNS box box-link-shadow"
> >
<div <div
className="ChangesetRow__Wrapper-tkpti5-0 bRWdJS" className="ChangesetRow__Wrapper-tkpti5-0 bRWdJS"
@@ -2418,7 +2418,7 @@ exports[`Storyshots Changesets With Committer 1`] = `
exports[`Storyshots Changesets With Committer and Co-Author 1`] = ` exports[`Storyshots Changesets With Committer and Co-Author 1`] = `
<div <div
className="Changesetsstories__Wrapper-sc-122npan-0 hHKBXk box box-link-shadow" className="Changesetsstories__Wrapper-sc-122npan-0 gDnzNS box box-link-shadow"
> >
<div <div
className="ChangesetRow__Wrapper-tkpti5-0 bRWdJS" className="ChangesetRow__Wrapper-tkpti5-0 bRWdJS"
@@ -2556,7 +2556,7 @@ exports[`Storyshots Changesets With Committer and Co-Author 1`] = `
exports[`Storyshots Changesets With avatar 1`] = ` exports[`Storyshots Changesets With avatar 1`] = `
<div <div
className="Changesetsstories__Wrapper-sc-122npan-0 hHKBXk box box-link-shadow" className="Changesetsstories__Wrapper-sc-122npan-0 gDnzNS box box-link-shadow"
> >
<div <div
className="ChangesetRow__Wrapper-tkpti5-0 bRWdJS" className="ChangesetRow__Wrapper-tkpti5-0 bRWdJS"
@@ -2684,9 +2684,138 @@ exports[`Storyshots Changesets With avatar 1`] = `
</div> </div>
`; `;
exports[`Storyshots Changesets With invalid signature 1`] = `
<div
className="Changesetsstories__Wrapper-sc-122npan-0 gDnzNS box box-link-shadow"
>
<div
className="ChangesetRow__Wrapper-tkpti5-0 bRWdJS"
>
<div
className="columns is-gapless is-mobile"
>
<div
className="column is-three-fifths"
>
<div
className="columns is-gapless"
>
<div
className="column is-four-fifths"
>
<div
className="media"
>
<div
className="ChangesetRow__Metadata-tkpti5-3 dfKqLe media-right"
>
<h4
className="has-text-weight-bold is-ellipsis-overflow"
>
initialize repository
</h4>
<p
className="is-hidden-touch"
/>
<p
className="is-hidden-desktop"
/>
<div
className="ChangesetRow__FlexRow-tkpti5-7 fTLhSo"
>
<p
className="ChangesetRow__AuthorWrapper-tkpti5-4 kDAubY is-size-7 is-ellipsis-overflow"
>
changeset.contributors.authoredBy
<a
href="mailto:scm-admin@scm-manager.org"
title="changeset.contributors.mailto scm-admin@scm-manager.org"
>
SCM Administrator
</a>
</p>
<span
className="tooltip has-tooltip-top"
data-tooltip="changeset.keyOwner: trillian
changeset.keyId: 0x247E908C6FD35473
changeset.signatureStatus: changeset.signatureInvalid
changeset.keyContacts:
- Tricia Marie McMilla <trillian@hitchhiker.com>"
>
<i
className="fas fa-key has-text-danger SignatureIcon__StyledIcon-sc-1mwf1m5-0 cgFYhq mx-2 pt-1"
/>
</span>
</div>
</div>
</div>
</div>
<div
className="ChangesetRow__VCenteredColumn-tkpti5-5 jtvbjX column"
/>
</div>
</div>
<div
className="ChangesetRow__VCenteredChildColumn-tkpti5-6 cciHUW column is-flex"
>
<div
className="ButtonAddons__Flex-sc-182golj-0 jSuMVB field has-addons is-marginless"
>
<p
className="control"
>
<button
className="button is-default is-reduced-mobile"
onClick={[Function]}
type="button"
>
<span
className="icon is-medium"
>
<i
className="fas fa-exchange-alt has-text-inherit"
/>
</span>
<span>
changeset.buttons.details
</span>
</button>
</p>
<p
className="control"
>
<button
className="button is-default is-reduced-mobile"
onClick={[Function]}
type="button"
>
<span
className="icon is-medium"
>
<i
className="fas fa-code has-text-inherit"
/>
</span>
<span>
changeset.buttons.sources
</span>
</button>
</p>
</div>
</div>
</div>
</div>
</div>
`;
exports[`Storyshots Changesets With multiple Co-Authors 1`] = ` exports[`Storyshots Changesets With multiple Co-Authors 1`] = `
<div <div
className="Changesetsstories__Wrapper-sc-122npan-0 hHKBXk box box-link-shadow" className="Changesetsstories__Wrapper-sc-122npan-0 gDnzNS box box-link-shadow"
> >
<div <div
className="ChangesetRow__Wrapper-tkpti5-0 bRWdJS" className="ChangesetRow__Wrapper-tkpti5-0 bRWdJS"
@@ -2814,6 +2943,261 @@ exports[`Storyshots Changesets With multiple Co-Authors 1`] = `
</div> </div>
`; `;
exports[`Storyshots Changesets With unknown signature 1`] = `
<div
className="Changesetsstories__Wrapper-sc-122npan-0 gDnzNS box box-link-shadow"
>
<div
className="ChangesetRow__Wrapper-tkpti5-0 bRWdJS"
>
<div
className="columns is-gapless is-mobile"
>
<div
className="column is-three-fifths"
>
<div
className="columns is-gapless"
>
<div
className="column is-four-fifths"
>
<div
className="media"
>
<div
className="ChangesetRow__Metadata-tkpti5-3 dfKqLe media-right"
>
<h4
className="has-text-weight-bold is-ellipsis-overflow"
>
initialize repository
</h4>
<p
className="is-hidden-touch"
/>
<p
className="is-hidden-desktop"
/>
<div
className="ChangesetRow__FlexRow-tkpti5-7 fTLhSo"
>
<p
className="ChangesetRow__AuthorWrapper-tkpti5-4 kDAubY is-size-7 is-ellipsis-overflow"
>
changeset.contributors.authoredBy
<a
href="mailto:scm-admin@scm-manager.org"
title="changeset.contributors.mailto scm-admin@scm-manager.org"
>
SCM Administrator
</a>
</p>
<span
className="tooltip has-tooltip-top"
data-tooltip="changeset.signatureStatus: changeset.signatureNotVerified
changeset.keyId: 0x247E908C6FD35473"
>
<i
className="fas fa-key has-text-grey-light SignatureIcon__StyledIcon-sc-1mwf1m5-0 cgFYhq mx-2 pt-1"
/>
</span>
</div>
</div>
</div>
</div>
<div
className="ChangesetRow__VCenteredColumn-tkpti5-5 jtvbjX column"
/>
</div>
</div>
<div
className="ChangesetRow__VCenteredChildColumn-tkpti5-6 cciHUW column is-flex"
>
<div
className="ButtonAddons__Flex-sc-182golj-0 jSuMVB field has-addons is-marginless"
>
<p
className="control"
>
<button
className="button is-default is-reduced-mobile"
onClick={[Function]}
type="button"
>
<span
className="icon is-medium"
>
<i
className="fas fa-exchange-alt has-text-inherit"
/>
</span>
<span>
changeset.buttons.details
</span>
</button>
</p>
<p
className="control"
>
<button
className="button is-default is-reduced-mobile"
onClick={[Function]}
type="button"
>
<span
className="icon is-medium"
>
<i
className="fas fa-code has-text-inherit"
/>
</span>
<span>
changeset.buttons.sources
</span>
</button>
</p>
</div>
</div>
</div>
</div>
</div>
`;
exports[`Storyshots Changesets With valid signature 1`] = `
<div
className="Changesetsstories__Wrapper-sc-122npan-0 gDnzNS box box-link-shadow"
>
<div
className="ChangesetRow__Wrapper-tkpti5-0 bRWdJS"
>
<div
className="columns is-gapless is-mobile"
>
<div
className="column is-three-fifths"
>
<div
className="columns is-gapless"
>
<div
className="column is-four-fifths"
>
<div
className="media"
>
<div
className="ChangesetRow__Metadata-tkpti5-3 dfKqLe media-right"
>
<h4
className="has-text-weight-bold is-ellipsis-overflow"
>
initialize repository
</h4>
<p
className="is-hidden-touch"
/>
<p
className="is-hidden-desktop"
/>
<div
className="ChangesetRow__FlexRow-tkpti5-7 fTLhSo"
>
<p
className="ChangesetRow__AuthorWrapper-tkpti5-4 kDAubY is-size-7 is-ellipsis-overflow"
>
changeset.contributors.authoredBy
<a
href="mailto:scm-admin@scm-manager.org"
title="changeset.contributors.mailto scm-admin@scm-manager.org"
>
SCM Administrator
</a>
</p>
<span
className="tooltip has-tooltip-top"
data-tooltip="changeset.keyOwner: trillian
changeset.keyId: 0x247E908C6FD35473
changeset.signatureStatus: changeset.signatureVerified
changeset.keyContacts:
- Tricia Marie McMilla <trillian@hitchhiker.com>"
>
<i
className="fas fa-key has-text-success SignatureIcon__StyledIcon-sc-1mwf1m5-0 cgFYhq mx-2 pt-1"
/>
</span>
</div>
</div>
</div>
</div>
<div
className="ChangesetRow__VCenteredColumn-tkpti5-5 jtvbjX column"
/>
</div>
</div>
<div
className="ChangesetRow__VCenteredChildColumn-tkpti5-6 cciHUW column is-flex"
>
<div
className="ButtonAddons__Flex-sc-182golj-0 jSuMVB field has-addons is-marginless"
>
<p
className="control"
>
<button
className="button is-default is-reduced-mobile"
onClick={[Function]}
type="button"
>
<span
className="icon is-medium"
>
<i
className="fas fa-exchange-alt has-text-inherit"
/>
</span>
<span>
changeset.buttons.details
</span>
</button>
</p>
<p
className="control"
>
<button
className="button is-default is-reduced-mobile"
onClick={[Function]}
type="button"
>
<span
className="icon is-medium"
>
<i
className="fas fa-code has-text-inherit"
/>
</span>
<span>
changeset.buttons.sources
</span>
</button>
</p>
</div>
</div>
</div>
</div>
</div>
`;
exports[`Storyshots Date Date from now 1`] = ` exports[`Storyshots Date Date from now 1`] = `
<div <div
className="Datestories__Wrapper-sc-1uda3v2-0 duRVzW" className="Datestories__Wrapper-sc-1uda3v2-0 duRVzW"
@@ -38972,6 +39356,15 @@ exports[`Storyshots Layout|Footer Default 1`] = `
profile.changePasswordNavLink profile.changePasswordNavLink
</a> </a>
</li> </li>
<li>
<a
className=""
href="/me/settings/publicKeys"
onClick={[Function]}
>
profile.publicKeysNavLink
</a>
</li>
</ul> </ul>
</section> </section>
<section <section
@@ -39088,6 +39481,15 @@ exports[`Storyshots Layout|Footer Full 1`] = `
profile.changePasswordNavLink profile.changePasswordNavLink
</a> </a>
</li> </li>
<li>
<a
className=""
href="/me/settings/publicKeys"
onClick={[Function]}
>
profile.publicKeysNavLink
</a>
</li>
<li> <li>
<a <a
className="" className=""
@@ -39240,6 +39642,15 @@ exports[`Storyshots Layout|Footer With Avatar 1`] = `
profile.changePasswordNavLink profile.changePasswordNavLink
</a> </a>
</li> </li>
<li>
<a
className=""
href="/me/settings/publicKeys"
onClick={[Function]}
>
profile.publicKeysNavLink
</a>
</li>
</ul> </ul>
</section> </section>
<section <section
@@ -39351,6 +39762,15 @@ exports[`Storyshots Layout|Footer With Plugin Links 1`] = `
profile.changePasswordNavLink profile.changePasswordNavLink
</a> </a>
</li> </li>
<li>
<a
className=""
href="/me/settings/publicKeys"
onClick={[Function]}
>
profile.publicKeysNavLink
</a>
</li>
<li> <li>
<a <a
className="" className=""

View File

@@ -69,10 +69,11 @@ const SignatureIcon: FC<Props> = ({ signatures, className }) => {
"changeset.signatureStatus" "changeset.signatureStatus"
)}: ${status}`; )}: ${status}`;
console.log(signature.contacts)
if (signature.contacts?.length > 0) { if (signature.contacts?.length > 0) {
message += `\n${t("changeset.keyContacts")}:`; message += `\n${t("changeset.keyContacts")}:`;
signature.contacts.forEach((contact) => { signature.contacts.forEach((contact) => {
message += `\n- ${contact}`; message += `\n- ${contact.name} <${contact.mail}>`;
}); });
} }
return message; return message;

View File

@@ -47,7 +47,7 @@ export type Signature = {
type: string; type: string;
status: "VERIFIED" | "NOT_FOUND" | "INVALID"; status: "VERIFIED" | "NOT_FOUND" | "INVALID";
owner: string; owner: string;
contacts: string[]; contacts: Person[];
} }
export type Contributor = { export type Contributor = {

View File

@@ -36,6 +36,7 @@ import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.repository.Person;
import sonia.scm.security.PublicKey; import sonia.scm.security.PublicKey;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@@ -52,9 +53,9 @@ public class GpgKey implements PublicKey {
private final String id; private final String id;
private final String owner; private final String owner;
private final String raw; private final String raw;
private final Set<String> contacts; private final Set<Person> contacts;
public GpgKey(String id, String owner, String raw, Set<String> contacts) { public GpgKey(String id, String owner, String raw, Set<Person> contacts) {
this.id = id; this.id = id;
this.owner = owner; this.owner = owner;
this.raw = raw; this.raw = raw;
@@ -80,7 +81,7 @@ public class GpgKey implements PublicKey {
} }
@Override @Override
public Set<String> getContacts() { public Set<Person> getContacts() {
return contacts; return contacts;
} }

View File

@@ -51,6 +51,7 @@ public abstract class PublicKeyMapper {
} }
@Mapping(target = "attributes", ignore = true) @Mapping(target = "attributes", ignore = true)
@Mapping(target = "raw", ignore = true)
abstract RawGpgKeyDto map(RawGpgKey rawGpgKey); abstract RawGpgKeyDto map(RawGpgKey rawGpgKey);
@ObjectFactory @ObjectFactory

View File

@@ -25,11 +25,11 @@
package sonia.scm.security.gpg; package sonia.scm.security.gpg;
import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.ContextEntry; import sonia.scm.ContextEntry;
import sonia.scm.event.ScmEventBus; import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.Person;
import sonia.scm.security.NotPublicKeyException; import sonia.scm.security.NotPublicKeyException;
import sonia.scm.security.PublicKeyCreatedEvent;
import sonia.scm.security.PublicKeyDeletedEvent; import sonia.scm.security.PublicKeyDeletedEvent;
import sonia.scm.store.DataStore; import sonia.scm.store.DataStore;
import sonia.scm.store.DataStoreFactory; import sonia.scm.store.DataStoreFactory;
@@ -38,6 +38,7 @@ import sonia.scm.user.UserPermissions;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@@ -80,22 +81,24 @@ public class PublicKeyStore {
RawGpgKey key = new RawGpgKey(master, displayName, username, rawKey, getContactsFromPublicKey(rawKey), Instant.now()); RawGpgKey key = new RawGpgKey(master, displayName, username, rawKey, getContactsFromPublicKey(rawKey), Instant.now());
store.put(master, key); store.put(master, key);
eventBus.post(new PublicKeyCreatedEvent());
return key; return key;
} }
private Set<String> getContactsFromPublicKey(String rawKey) { private Set<Person> getContactsFromPublicKey(String rawKey) {
Set<String> contacts = new HashSet<>(); List<String> userIds = new ArrayList<>();
Optional<PGPPublicKey> publicKeyFromRawKey = getFromRawKey(rawKey); Optional<PGPPublicKey> publicKeyFromRawKey = getFromRawKey(rawKey);
publicKeyFromRawKey.ifPresent(pgpPublicKey -> pgpPublicKey.getUserIDs().forEachRemaining(contacts::add)); publicKeyFromRawKey.ifPresent(pgpPublicKey -> pgpPublicKey.getUserIDs().forEachRemaining(userIds::add));
return contacts;
return userIds.stream().map(Person::toPerson).collect(Collectors.toSet());
} }
public void delete(String id) { public void delete(String id) {
RawGpgKey rawGpgKey = store.get(id); RawGpgKey rawGpgKey = store.get(id);
if (rawGpgKey != null) { if (rawGpgKey != null) {
UserPermissions.modify(rawGpgKey.getOwner()).check(); UserPermissions.changePublicKeys(rawGpgKey.getOwner()).check();
store.remove(id); store.remove(id);
eventBus.post(new PublicKeyDeletedEvent()); eventBus.post(new PublicKeyDeletedEvent());
} }

View File

@@ -28,6 +28,7 @@ import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import sonia.scm.repository.Person;
import sonia.scm.xml.XmlInstantAdapter; import sonia.scm.xml.XmlInstantAdapter;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
@@ -49,7 +50,7 @@ public class RawGpgKey {
private String displayName; private String displayName;
private String owner; private String owner;
private String raw; private String raw;
private Set<String> contacts; private Set<Person> contacts;
@XmlJavaTypeAdapter(XmlInstantAdapter.class) @XmlJavaTypeAdapter(XmlInstantAdapter.class)
private Instant created; private Instant created;

View File

@@ -31,6 +31,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.repository.Person;
import sonia.scm.security.PublicKey; import sonia.scm.security.PublicKey;
import java.io.IOException; import java.io.IOException;
@@ -52,16 +53,17 @@ class DefaultGPGTest {
@Test @Test
void shouldFindIdInSignature() throws IOException { void shouldFindIdInSignature() throws IOException {
String raw = GPGTestHelper.readResourceAsString("signature.asc"); String raw = GPGTestHelper.readResourceAsString("slarti.txt.asc");
String publicKeyId = gpg.findPublicKeyId(raw.getBytes()); String publicKeyId = gpg.findPublicKeyId(raw.getBytes());
assertThat(publicKeyId).isEqualTo("0x1F17B79A09DAD5B9"); assertThat(publicKeyId).isEqualTo("0x247E908C6FD35473");
} }
@Test @Test
void shouldFindPublicKey() throws IOException { void shouldFindPublicKey() throws IOException {
String raw = GPGTestHelper.readResourceAsString("subkeys.asc"); 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 <tricia.mcmillan@hitchhiker.org>");
RawGpgKey key1 = new RawGpgKey("42", "key_42", "trillian", raw, ImmutableSet.of(trillian), Instant.now());
when(store.findById("42")).thenReturn(Optional.of(key1)); when(store.findById("42")).thenReturn(Optional.of(key1));
@@ -71,7 +73,7 @@ class DefaultGPGTest {
assertThat(publicKey.get().getOwner()).isPresent(); assertThat(publicKey.get().getOwner()).isPresent();
assertThat(publicKey.get().getOwner().get()).contains("trillian"); assertThat(publicKey.get().getOwner().get()).contains("trillian");
assertThat(publicKey.get().getId()).isEqualTo("42"); assertThat(publicKey.get().getId()).isEqualTo("42");
assertThat(publicKey.get().getContacts()).contains("trillian", "zaphod"); assertThat(publicKey.get().getContacts()).contains(trillian);
} }
@Test @Test

View File

@@ -36,12 +36,12 @@ class PgpPublicKeyExtractorTest {
@Test @Test
void shouldExtractPublicKeyFromRawKey() throws IOException { void shouldExtractPublicKeyFromRawKey() throws IOException {
String raw = GPGTestHelper.readResourceAsString("pubKeyEH.asc"); String raw = GPGTestHelper.readResourceAsString("single.asc");
Optional<PGPPublicKey> publicKey = PgpPublicKeyExtractor.getFromRawKey(raw); Optional<PGPPublicKey> publicKey = PgpPublicKeyExtractor.getFromRawKey(raw);
assertThat(publicKey).isPresent(); assertThat(publicKey).isPresent();
assertThat(Long.toHexString(publicKey.get().getKeyID())).isEqualTo("39ad4bed55527f1c"); assertThat(Long.toHexString(publicKey.get().getKeyID())).isEqualTo("975922f193b07d6e");
} }
} }

View File

@@ -75,7 +75,6 @@ class PublicKeyMapperTest {
RawGpgKeyDto dto = mapper.map(key); RawGpgKeyDto dto = mapper.map(key);
assertThat(dto.getDisplayName()).isEqualTo(key.getDisplayName()); assertThat(dto.getDisplayName()).isEqualTo(key.getDisplayName());
assertThat(dto.getRaw()).isEqualTo(key.getRaw());
assertThat(dto.getCreated()).isEqualTo(key.getCreated()); assertThat(dto.getCreated()).isEqualTo(key.getCreated());
assertThat(dto.getLinks().getLinkBy("self").get().getHref()).isEqualTo("/v2/public_keys/1"); 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"); assertThat(dto.getLinks().getLinkBy("delete").get().getHref()).isEqualTo("/v2/public_keys/delete/1");

View File

@@ -34,7 +34,9 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.event.ScmEventBus; import sonia.scm.event.ScmEventBus;
import sonia.scm.repository.Person;
import sonia.scm.security.NotPublicKeyException; import sonia.scm.security.NotPublicKeyException;
import sonia.scm.security.PublicKeyCreatedEvent;
import sonia.scm.security.PublicKeyDeletedEvent; import sonia.scm.security.PublicKeyDeletedEvent;
import sonia.scm.store.DataStoreFactory; import sonia.scm.store.DataStoreFactory;
import sonia.scm.store.InMemoryDataStoreFactory; import sonia.scm.store.InMemoryDataStoreFactory;
@@ -103,6 +105,9 @@ class PublicKeyStoreTest {
assertThat(key.getOwner()).isEqualTo("trillian"); assertThat(key.getOwner()).isEqualTo("trillian");
assertThat(key.getCreated()).isAfterOrEqualTo(now); assertThat(key.getCreated()).isAfterOrEqualTo(now);
assertThat(key.getRaw()).isEqualTo(rawKey); assertThat(key.getRaw()).isEqualTo(rawKey);
assertThat(key.getContacts()).contains(Person.toPerson("SCM Packages (signing key for packages.scm-manager.org) <scm-team@cloudogu.com>"));
verify(eventBus).post(any(PublicKeyCreatedEvent.class));
} }
@Test @Test