diff --git a/scm-core/src/main/java/sonia/scm/security/NotPublicKeyException.java b/scm-core/src/main/java/sonia/scm/security/NotPublicKeyException.java new file mode 100644 index 0000000000..2433b31429 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/security/NotPublicKeyException.java @@ -0,0 +1,45 @@ +/* + * 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.BadRequestException; +import sonia.scm.ContextEntry; + +import java.util.List; + +public class NotPublicKeyException extends BadRequestException { + public NotPublicKeyException(List context, String message) { + super(context, message); + } + + public NotPublicKeyException(List context, String message, Exception cause) { + super(context, message, cause); + } + + @Override + public String getCode() { + return "BxS5wX2v71"; + } +} diff --git a/scm-core/src/main/java/sonia/scm/user/User.java b/scm-core/src/main/java/sonia/scm/user/User.java index cd2afb282d..1b0a3b2388 100644 --- a/scm-core/src/main/java/sonia/scm/user/User.java +++ b/scm-core/src/main/java/sonia/scm/user/User.java @@ -50,7 +50,7 @@ import java.security.Principal; @StaticPermissions( value = "user", globalPermissions = {"create", "list", "autocomplete"}, - permissions = {"read", "modify", "delete", "changePassword"}, + permissions = {"read", "modify", "delete", "changePassword", "changePublicKeys"}, custom = true, customGlobal = true ) @XmlRootElement(name = "users") diff --git a/scm-ui/ui-webapp/public/locales/de/users.json b/scm-ui/ui-webapp/public/locales/de/users.json index fd744e768d..8cb79d6d6f 100644 --- a/scm-ui/ui-webapp/public/locales/de/users.json +++ b/scm-ui/ui-webapp/public/locales/de/users.json @@ -37,7 +37,8 @@ "settingsNavLink": "Einstellungen", "generalNavLink": "Generell", "setPasswordNavLink": "Passwort", - "setPermissionsNavLink": "Berechtigungen" + "setPermissionsNavLink": "Berechtigungen", + "setPublicKeyNavLink": "Öffentliche Schlüssel" } }, "createUser": { @@ -60,5 +61,13 @@ "userForm": { "subtitle": "Benutzer bearbeiten", "button": "Speichern" + }, + "publicKey": { + "noStoredKeys": "Es wurden keine Schlüssel gefunden.", + "displayName": "Anzeigename", + "raw": "Schlüssel", + "created": "Eingetragen an", + "addKey": "Schlüssel hinzufügen", + "delete": "Löschen" } } diff --git a/scm-ui/ui-webapp/public/locales/en/users.json b/scm-ui/ui-webapp/public/locales/en/users.json index 3353977a18..0db2b9175f 100644 --- a/scm-ui/ui-webapp/public/locales/en/users.json +++ b/scm-ui/ui-webapp/public/locales/en/users.json @@ -37,7 +37,8 @@ "settingsNavLink": "Settings", "generalNavLink": "General", "setPasswordNavLink": "Password", - "setPermissionsNavLink": "Permissions" + "setPermissionsNavLink": "Permissions", + "setPublicKeyNavLink": "Public Keys" } }, "createUser": { @@ -60,5 +61,13 @@ "userForm": { "subtitle": "Edit User", "button": "Submit" + }, + "publicKey": { + "noStoredKeys": "No keys found.", + "displayName": "Display Name", + "raw": "Key", + "created": "Created on", + "addKey": "Add key", + "delete": "Delete" } } diff --git a/scm-ui/ui-webapp/src/containers/Profile.tsx b/scm-ui/ui-webapp/src/containers/Profile.tsx index 8f14bed1e7..e0700306f8 100644 --- a/scm-ui/ui-webapp/src/containers/Profile.tsx +++ b/scm-ui/ui-webapp/src/containers/Profile.tsx @@ -42,6 +42,8 @@ import { import ChangeUserPassword from "./ChangeUserPassword"; import ProfileInfo from "./ProfileInfo"; import { ExtensionPoint } from "@scm-manager/ui-extensions"; +import SetPublicKeys from "../users/components/publicKeys/SetPublicKeys"; +import SetPublicKeyNavLink from "../users/components/navLinks/SetPublicKeysNavLink"; type Props = RouteComponentProps & WithTranslation & { @@ -93,6 +95,7 @@ class Profile extends React.Component { } /> } /> + } /> @@ -109,6 +112,7 @@ class Profile extends React.Component { title={t("profile.settingsNavLink")} > + diff --git a/scm-ui/ui-webapp/src/users/components/navLinks/SetPublicKeysNavLink.tsx b/scm-ui/ui-webapp/src/users/components/navLinks/SetPublicKeysNavLink.tsx new file mode 100644 index 0000000000..f3144442f5 --- /dev/null +++ b/scm-ui/ui-webapp/src/users/components/navLinks/SetPublicKeysNavLink.tsx @@ -0,0 +1,43 @@ +/* + * 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. + */ +import React, { FC } from "react"; +import { Link, User, Me } from "@scm-manager/ui-types"; +import { NavLink } from "@scm-manager/ui-components"; +import { useTranslation } from "react-i18next"; + +type Props = { + user: User | Me; + publicKeyUrl: string; +}; + +const SetPublicKeyNavLink: FC = ({ user, publicKeyUrl }) => { + const [t] = useTranslation("users"); + + if ((user?._links?.publicKeys as Link)?.href) { + return ; + } + return null; +}; + +export default SetPublicKeyNavLink; diff --git a/scm-ui/ui-webapp/src/users/components/navLinks/index.ts b/scm-ui/ui-webapp/src/users/components/navLinks/index.ts index 0ccd16b42a..f732ea83ee 100644 --- a/scm-ui/ui-webapp/src/users/components/navLinks/index.ts +++ b/scm-ui/ui-webapp/src/users/components/navLinks/index.ts @@ -25,3 +25,4 @@ export { default as EditUserNavLink } from "./EditUserNavLink"; export { default as SetPasswordNavLink } from "./SetPasswordNavLink"; export { default as SetPermissionsNavLink } from "./SetPermissionsNavLink"; +export { default as SetPublicKeysNavLink } from "./SetPublicKeysNavLink"; diff --git a/scm-ui/ui-webapp/src/users/components/publicKeys/AddPublicKey.tsx b/scm-ui/ui-webapp/src/users/components/publicKeys/AddPublicKey.tsx new file mode 100644 index 0000000000..e0129e8245 --- /dev/null +++ b/scm-ui/ui-webapp/src/users/components/publicKeys/AddPublicKey.tsx @@ -0,0 +1,89 @@ +/* + * 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. + */ + +import React, { FC, useState } from "react"; +import { User, Link, Links, Collection } from "@scm-manager/ui-types/src"; +import { + ErrorNotification, + InputField, + Level, + Textarea, + SubmitButton, + apiClient, + Loading +} from "@scm-manager/ui-components"; +import { useTranslation } from "react-i18next"; +import { CONTENT_TYPE_PUBLIC_KEY } from "./SetPublicKeys"; + +type Props = { + createLink: string; + refresh: () => void; +}; + +const AddPublicKey: FC = ({ createLink, refresh }) => { + const [t] = useTranslation("users"); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(); + const [displayName, setDisplayName] = useState(""); + const [raw, setRaw] = useState(""); + + const isValid = () => { + return !!displayName && !!raw; + }; + + const resetForm = () => { + setDisplayName(""); + setRaw(""); + }; + + const addKey = () => { + setLoading(true); + apiClient + .post(createLink, { displayName: displayName, raw: raw }, CONTENT_TYPE_PUBLIC_KEY) + .then(resetForm) + .then(refresh) + .then(() => setLoading(false)) + .catch(setError); + }; + + if (error) { + return ; + } + + if (loading) { + return ; + } + + return ( + <> + +