From a7c4d41e4e8d2d19403b2360a1a8c67da5c95de0 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Thu, 26 Nov 2020 12:57:40 +0100 Subject: [PATCH] improve error handling on repository import if the credentials were wrong or missing --- .../repository/api/ImportFailedException.java | 44 +++++++++++++++++ .../scm/repository/spi/GitPullCommand.java | 30 +++++++----- .../repos/components/form/RepositoryForm.tsx | 13 +++-- .../src/repos/containers/AddRepository.tsx | 47 ++++++++++--------- .../resources/RepositoryImportResource.java | 6 ++- .../main/resources/locales/de/plugins.json | 10 ++-- .../main/resources/locales/en/plugins.json | 4 ++ 7 files changed, 111 insertions(+), 43 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/repository/api/ImportFailedException.java diff --git a/scm-core/src/main/java/sonia/scm/repository/api/ImportFailedException.java b/scm-core/src/main/java/sonia/scm/repository/api/ImportFailedException.java new file mode 100644 index 0000000000..a990d6c760 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/api/ImportFailedException.java @@ -0,0 +1,44 @@ +/* + * 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.repository.api; + +import sonia.scm.ContextEntry; +import sonia.scm.ExceptionWithContext; + +import java.util.List; + +public class ImportFailedException extends ExceptionWithContext { + + private static final String CODE = "D6SHRfqQw1"; + + public ImportFailedException(List context, String message, Exception cause) { + super(context, message, cause); + } + + @Override + public String getCode() { + return CODE; + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java index 679efd943a..6a3b42a335 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java @@ -29,7 +29,6 @@ package sonia.scm.repository.spi; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.Iterables; -import org.eclipse.jgit.api.FetchCommand; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.ObjectId; @@ -41,10 +40,12 @@ import org.eclipse.jgit.transport.TrackingRefUpdate; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.ContextEntry; import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.GitUtil; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Repository; +import sonia.scm.repository.api.ImportFailedException; import sonia.scm.repository.api.PullResponse; import javax.inject.Inject; @@ -200,15 +201,13 @@ public class GitPullCommand extends AbstractGitPushOrPullCommand try { //J- - FetchCommand fetchCommand = git.fetch(); - - if (!Strings.isNullOrEmpty(request.getUsername()) && !Strings.isNullOrEmpty(request.getPassword())) { - fetchCommand.setCredentialsProvider( - new UsernamePasswordCredentialsProvider(request.getUsername(), request.getPassword()) - ); - } - - FetchResult result = fetchCommand + FetchResult result = git.fetch() + .setCredentialsProvider( + new UsernamePasswordCredentialsProvider( + Strings.nullToEmpty(request.getUsername()), + Strings.nullToEmpty(request.getPassword()) + ) + ) .setRefSpecs(new RefSpec(REF_SPEC)) .setRemote(request.getRemoteUrl().toExternalForm()) .setTagOpt(TagOpt.FETCH_TAGS) @@ -216,7 +215,16 @@ public class GitPullCommand extends AbstractGitPushOrPullCommand //J+ response = convert(git, result); - } catch (GitAPIException ex) { + } catch + (GitAPIException ex) { + if (ex.getMessage().contains("not authorized")) { + throw new ImportFailedException( + ContextEntry.ContextBuilder.entity(repository).build(), + "Repository import failed. The credentials are wrong or missing.", + ex + ); + } + throw new InternalRepositoryException(repository, "error during pull", ex); } diff --git a/scm-ui/ui-webapp/src/repos/components/form/RepositoryForm.tsx b/scm-ui/ui-webapp/src/repos/components/form/RepositoryForm.tsx index 2dad770273..d8e03a43c9 100644 --- a/scm-ui/ui-webapp/src/repos/components/form/RepositoryForm.tsx +++ b/scm-ui/ui-webapp/src/repos/components/form/RepositoryForm.tsx @@ -108,6 +108,7 @@ const RepositoryForm: FC = ({ const isCreateMode = () => creationMode === "CREATE"; const isEditMode = () => !!repository; const isModifiable = () => !!repository && !!repository._links.update; + const disabled = (!isModifiable() && isEditMode()) || loading; const isValid = () => { return !( @@ -176,6 +177,7 @@ const RepositoryForm: FC = ({ onChange={handleImportUrlChange} value={importUrl} helpText={t("help.importUrlHelpText")} + disabled={disabled} /> @@ -184,6 +186,7 @@ const RepositoryForm: FC = ({ onChange={setUsername} value={username} helpText={t("help.usernameHelpText")} + disabled={disabled} /> @@ -193,6 +196,7 @@ const RepositoryForm: FC = ({ value={password} type="password" helpText={t("help.passwordHelpText")} + disabled={disabled} /> @@ -221,6 +225,7 @@ const RepositoryForm: FC = ({ validationError={nameValidationError} errorMessage={t("validation.name-invalid")} helpText={t("help.nameHelpText")} + disabled={disabled} /> @@ -230,6 +235,7 @@ const RepositoryForm: FC = ({ value={repo ? repo.type : ""} options={createSelectOptions(repositoryTypes)} helpText={t("help.typeHelpText")} + disabled={disabled} /> {!isImportMode() && ( @@ -239,6 +245,7 @@ const RepositoryForm: FC = ({ checked={initRepository} onChange={toggleInitCheckbox} helpText={t("help.initializeRepository")} + disabled={disabled} /> {initRepository && ( @@ -287,16 +294,12 @@ const RepositoryForm: FC = ({ setImportUrl(url); }; - const disabled = !isModifiable() && isEditMode(); - const submitButton = () => { if (disabled) { return null; } const translationKey = isImportMode() ? "repositoryForm.submitImport" : "repositoryForm.submitCreate"; - return } - />; + return } />; }; return ( diff --git a/scm-ui/ui-webapp/src/repos/containers/AddRepository.tsx b/scm-ui/ui-webapp/src/repos/containers/AddRepository.tsx index 800f63d502..5478804a81 100644 --- a/scm-ui/ui-webapp/src/repos/containers/AddRepository.tsx +++ b/scm-ui/ui-webapp/src/repos/containers/AddRepository.tsx @@ -150,29 +150,30 @@ class AddRepository extends React.Component { error={error} showContentOnError={true} > - {importLoading ? ( - <> - {t("import.pending.infoText")} - - - ) : ( - <> - {!error && } - { - createRepo(repoLink, repo, initRepository, (repo: Repository) => this.repoCreated(repo)); - }} - importRepository={repo => { - importRepoFromUrl(repoLink, repo, (repo: Repository) => this.repoCreated(repo)); - }} - indexResources={indexResources} - creationMode={this.isImportPage() ? "IMPORT" : "CREATE"} - /> - - )} + <> + {/*//TODO fix this CSS*/} + {!error && } + {importLoading && ( + <> + {t("import.pending.infoText")} + +
+ + )} + { + createRepo(repoLink, repo, initRepository, (repo: Repository) => this.repoCreated(repo)); + }} + importRepository={repo => { + importRepoFromUrl(repoLink, repo, (repo: Repository) => this.repoCreated(repo)); + }} + indexResources={indexResources} + creationMode={this.isImportPage() ? "IMPORT" : "CREATE"} + /> + ); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryImportResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryImportResource.java index f16bd611e6..a319f94457 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryImportResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryImportResource.java @@ -46,6 +46,7 @@ import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.RepositoryType; import sonia.scm.repository.api.Command; +import sonia.scm.repository.api.ImportFailedException; import sonia.scm.repository.api.PullCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; @@ -216,9 +217,12 @@ public class RepositoryImportResource { } pullCommand.pull(request.getUrl()); + } catch (ImportFailedException ex) { + handleImportFailure(ex, repository); + throw ex; } catch (Exception ex) { handleImportFailure(ex, repository); - throw new InternalRepositoryException(repository, "Import failed. Most likely the credentials are wrong or missing.", ex); + throw new InternalRepositoryException(repository, "Repository Import failed.", ex); } return Response.created(URI.create(resourceLinks.repository().self(repository.getNamespace(), repository.getName()))).build(); diff --git a/scm-webapp/src/main/resources/locales/de/plugins.json b/scm-webapp/src/main/resources/locales/de/plugins.json index 300fa3d17a..f882236e3e 100644 --- a/scm-webapp/src/main/resources/locales/de/plugins.json +++ b/scm-webapp/src/main/resources/locales/de/plugins.json @@ -286,12 +286,16 @@ "FVS9JY1T21": { "displayName": "Fehler bei der Anfrage", "description": "Bei der Anfrage trat ein Fehler auf. Prüfen Sie bitte den Status der HTTP Antwort und die konkrete Meldung." + }, + "D6SHRfqQw1": { + "displayName": "Repository Import fehlgeschlagen", + "description": "Das Repository konnte nicht importiert werden. Entweder wurden die Zugangsdaten (Benutzername/Passwort) nicht gesetzt oder sind fehlerhaft. Bitte prüfen Sie Ihre Eingaben." } }, "namespaceStrategies": { - "UsernameNamespaceStrategy": "Benutzername", - "CustomNamespaceStrategy": "Benutzerdefiniert", - "CurrentYearNamespaceStrategy": "Aktuelles Jahr", + "UsernameNamespaceStrategy": "Benutzername", + "CustomNamespaceStrategy": "Benutzerdefiniert", + "CurrentYearNamespaceStrategy": "Aktuelles Jahr", "RepositoryTypeNamespaceStrategy": "Repository Typ" } } diff --git a/scm-webapp/src/main/resources/locales/en/plugins.json b/scm-webapp/src/main/resources/locales/en/plugins.json index 8d6086ac19..e35c8942c7 100644 --- a/scm-webapp/src/main/resources/locales/en/plugins.json +++ b/scm-webapp/src/main/resources/locales/en/plugins.json @@ -286,6 +286,10 @@ "FVS9JY1T21": { "displayName": "Error in the request", "description": "While processing the request there was an error. Please check the http return status and the concrete error message." + }, + "D6SHRfqQw1": { + "displayName": "Repository import failed", + "description": "The repository could not be imported. Either the credentials (username/password) are wrong or missing. Please check your inputs." } }, "namespaceStrategies": {