diff --git a/scm-ui/ui-components/src/repos/changesets/SignatureIcon.tsx b/scm-ui/ui-components/src/repos/changesets/SignatureIcon.tsx index 2770dbc371..65ad8453a0 100644 --- a/scm-ui/ui-components/src/repos/changesets/SignatureIcon.tsx +++ b/scm-ui/ui-components/src/repos/changesets/SignatureIcon.tsx @@ -69,7 +69,6 @@ const SignatureIcon: FC = ({ signatures, className }) => { "changeset.signatureStatus" )}: ${status}`; - console.log(signature.contacts) if (signature.contacts?.length > 0) { message += `\n${t("changeset.keyContacts")}:`; signature.contacts.forEach((contact) => { diff --git a/scm-ui/ui-webapp/public/locales/de/users.json b/scm-ui/ui-webapp/public/locales/de/users.json index 8cb79d6d6f..825876ec47 100644 --- a/scm-ui/ui-webapp/public/locales/de/users.json +++ b/scm-ui/ui-webapp/public/locales/de/users.json @@ -68,6 +68,7 @@ "raw": "Schlüssel", "created": "Eingetragen an", "addKey": "Schlüssel hinzufügen", - "delete": "Löschen" + "delete": "Löschen", + "download": "Herunterladen" } } diff --git a/scm-ui/ui-webapp/public/locales/en/users.json b/scm-ui/ui-webapp/public/locales/en/users.json index 0db2b9175f..d50618bcf0 100644 --- a/scm-ui/ui-webapp/public/locales/en/users.json +++ b/scm-ui/ui-webapp/public/locales/en/users.json @@ -68,6 +68,7 @@ "raw": "Key", "created": "Created on", "addKey": "Add key", - "delete": "Delete" + "delete": "Delete", + "download": "Download" } } diff --git a/scm-ui/ui-webapp/src/users/components/publicKeys/PublicKeyEntry.tsx b/scm-ui/ui-webapp/src/users/components/publicKeys/PublicKeyEntry.tsx index cea7514ab3..108f53873d 100644 --- a/scm-ui/ui-webapp/src/users/components/publicKeys/PublicKeyEntry.tsx +++ b/scm-ui/ui-webapp/src/users/components/publicKeys/PublicKeyEntry.tsx @@ -23,11 +23,10 @@ */ import React, { FC } from "react"; -import { DateFromNow, DeleteButton } from "@scm-manager/ui-components/src"; +import {DateFromNow, DeleteButton, DownloadButton} from "@scm-manager/ui-components/src"; import { PublicKey } from "./SetPublicKeys"; import { useTranslation } from "react-i18next"; import { Link } from "@scm-manager/ui-types"; -import { formatPublicKey } from "./formatPublicKey"; type Props = { publicKey: PublicKey; @@ -43,6 +42,12 @@ export const PublicKeyEntry: FC = ({ publicKey, onDelete }) => { onDelete((publicKey._links.delete as Link).href)} /> ); } + let downloadButton; + if (publicKey?._links?.raw) { + downloadButton = ( + + ); + } return ( <> @@ -53,6 +58,7 @@ export const PublicKeyEntry: FC = ({ publicKey, onDelete }) => { {publicKey.id} {deleteButton} + {downloadButton} ); diff --git a/scm-ui/ui-webapp/src/users/components/publicKeys/PublicKeyTable.tsx b/scm-ui/ui-webapp/src/users/components/publicKeys/PublicKeyTable.tsx index 3172b1429a..2270f10825 100644 --- a/scm-ui/ui-webapp/src/users/components/publicKeys/PublicKeyTable.tsx +++ b/scm-ui/ui-webapp/src/users/components/publicKeys/PublicKeyTable.tsx @@ -33,7 +33,7 @@ type Props = { onDelete: (link: string) => void; }; -const PublicKeyTable: FC = ({ publicKeys, onDelete }) => { +const PublicKeyTable: FC = ({ publicKeys, onDelete, onDownload }) => { const [t] = useTranslation("users"); if (publicKeys?._embedded?.keys?.length === 0) { 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 91af754a3a..5b3097a785 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 @@ -61,6 +61,7 @@ public abstract class PublicKeyMapper { if (UserPermissions.changePublicKeys(rawGpgKey.getOwner()).isPermitted() && !rawGpgKey.isReadonly()) { linksBuilder.single(Link.link("delete", createDeleteLink(rawGpgKey))); } + linksBuilder.single(Link.link("raw", createDownloadLink(rawGpgKey))); return new RawGpgKeyDto(linksBuilder.build()); } @@ -77,4 +78,11 @@ public abstract class PublicKeyMapper { .parameters(rawGpgKey.getOwner(), rawGpgKey.getId()) .href(); } + + private String createDownloadLink(RawGpgKey rawGpgKey) { + return new LinkBuilder(scmPathInfoStore.get().get(), PublicKeyResource.class) + .method("findByIdGpg") + .parameters(rawGpgKey.getId()) + .href(); + } } diff --git a/scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyResource.java b/scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyResource.java index 0876207144..c955f63d1c 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyResource.java +++ b/scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyResource.java @@ -90,7 +90,9 @@ public class PublicKeyResource { public Response findByIdGpg(@PathParam("id") String id) { Optional byId = store.findById(id); if (byId.isPresent()) { - return Response.ok(byId.get().getRaw()).build(); + return Response.ok(byId.get().getRaw()) + .header("Content-Disposition", "attachment; filename=\"" + byId.get().getDisplayName() + ".asc\"") + .build(); } return Response.status(Response.Status.NOT_FOUND).build(); } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeDtoFactoryTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeDtoFactoryTest.java index d82e153355..5a3173cfc8 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeDtoFactoryTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeDtoFactoryTest.java @@ -206,7 +206,7 @@ class MeDtoFactoryTest { when(subject.isPermitted("user:changePublicKeys:trillian")).thenReturn(true); MeDto dto = meDtoFactory.create(); - assertThat(dto.getLinks().getLinkBy("publicKeys").get().getHref()).isEqualTo("https://scm.hitchhiker.com/scm/v2/public_keys/trillian"); + assertThat(dto.getLinks().getLinkBy("publicKeys").get().getHref()).isEqualTo("https://scm.hitchhiker.com/scm/v2/users/trillian/public_keys"); } @Test diff --git a/scm-webapp/src/test/java/sonia/scm/security/gpg/PublicKeyCollectionMapperTest.java b/scm-webapp/src/test/java/sonia/scm/security/gpg/PublicKeyCollectionMapperTest.java index 5fe72825c4..bb8ec5e5ad 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/gpg/PublicKeyCollectionMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/gpg/PublicKeyCollectionMapperTest.java @@ -86,7 +86,7 @@ class PublicKeyCollectionMapperTest { List embedded = collection.getEmbedded().getItemsBy("keys"); assertThat(embedded).hasSize(2); - assertThat(collection.getLinks().getLinkBy("self").get().getHref()).isEqualTo("/v2/public_keys/trillian"); + assertThat(collection.getLinks().getLinkBy("self").get().getHref()).isEqualTo("/v2/users/trillian/public_keys"); } @Test @@ -94,7 +94,7 @@ class PublicKeyCollectionMapperTest { when(subject.isPermitted("user:changePublicKeys:trillian")).thenReturn(true); HalRepresentation collection = collectionMapper.map("trillian", Lists.newArrayList()); - assertThat(collection.getLinks().getLinkBy("create").get().getHref()).isEqualTo("/v2/public_keys/trillian"); + assertThat(collection.getLinks().getLinkBy("create").get().getHref()).isEqualTo("/v2/users/trillian/public_keys"); } @Test 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 d4d6eb67e5..d67125ffe4 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 @@ -76,8 +76,9 @@ class PublicKeyMapperTest { assertThat(dto.getDisplayName()).isEqualTo(key.getDisplayName()); 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"); + assertThat(dto.getLinks().getLinkBy("self").get().getHref()).isEqualTo("/v2/users/trillian/public_keys/1"); + assertThat(dto.getLinks().getLinkBy("delete").get().getHref()).isEqualTo("/v2/users/trillian/public_keys/1"); + assertThat(dto.getLinks().getLinkBy("raw").get().getHref()).isEqualTo("/v2/public_keys/1"); } @Test