From b30817a4abde488fda0414b5c1d38ba7fcdd1168 Mon Sep 17 00:00:00 2001 From: Erin Harris Date: Tue, 2 Jun 2020 16:34:19 -0400 Subject: [PATCH] CW Issue #2647: Support authorization for template sources --- .../codewind/core/internal/HttpUtil.java | 35 +- .../codewind/core/internal/IAuthInfo.java | 21 ++ .../codewind/core/internal/cli/AuthToken.java | 15 +- .../codewind/core/internal/cli/CLIUtil.java | 15 +- .../core/internal/cli/TemplateUtil.java | 25 +- .../internal/connection/JSONObjectResult.java | 2 +- .../internal/connection/RepositoryInfo.java | 15 + .../eclipse/codewind/ui/internal/IDEUtil.java | 5 + .../ui/internal/messages/Messages.java | 36 +- .../ui/internal/messages/messages.properties | 44 ++- .../prefs/AddTemplateSourceWizard.java | 160 +++++++++ .../prefs/EditTemplateSourceWizard.java | 36 ++ .../prefs/RepositoryManagementComposite.java | 295 +++-------------- .../prefs/TemplateSourceAuthComposite.java | 310 ++++++++++++++++++ .../prefs/TemplateSourceAuthDialog.java | 126 +++++++ .../prefs/TemplateSourceAuthPage.java | 109 ++++++ .../prefs/TemplateSourceDetailsPage.java | 247 ++++++++++++++ .../internal/prefs/TemplateSourceURLPage.java | 135 ++++++++ .../wizards/NewCodewindProjectWizard.java | 71 +++- 19 files changed, 1422 insertions(+), 280 deletions(-) create mode 100644 dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/IAuthInfo.java create mode 100644 dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/AddTemplateSourceWizard.java create mode 100644 dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/EditTemplateSourceWizard.java create mode 100644 dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/TemplateSourceAuthComposite.java create mode 100644 dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/TemplateSourceAuthDialog.java create mode 100644 dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/TemplateSourceAuthPage.java create mode 100644 dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/TemplateSourceDetailsPage.java create mode 100644 dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/TemplateSourceURLPage.java diff --git a/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/HttpUtil.java b/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/HttpUtil.java index 97c1a90b2..a3bf7926e 100644 --- a/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/HttpUtil.java +++ b/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/HttpUtil.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018, 2019 IBM Corporation and others. + * Copyright (c) 2018, 2020 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -27,7 +27,6 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; -import org.eclipse.codewind.core.internal.cli.AuthToken; import org.json.JSONArray; import org.json.JSONObject; @@ -142,55 +141,55 @@ public static HttpResult get(URI uri) throws IOException { return get(uri, null); } - public static HttpResult get(URI uri, AuthToken auth) throws IOException { + public static HttpResult get(URI uri, IAuthInfo auth) throws IOException { return sendRequest("GET", uri, auth, null); } - public static HttpResult get(URI uri, AuthToken auth, int connectTimeoutMS, int readTimeoutMS) throws IOException { + public static HttpResult get(URI uri, IAuthInfo auth, int connectTimeoutMS, int readTimeoutMS) throws IOException { return sendRequest("GET", uri, auth, null, connectTimeoutMS, readTimeoutMS); } - public static HttpResult post(URI uri, AuthToken auth, JSONObject payload) throws IOException { + public static HttpResult post(URI uri, IAuthInfo auth, JSONObject payload) throws IOException { return sendRequest("POST", uri, auth, payload, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS); } - public static HttpResult post(URI uri, AuthToken auth, JSONObject payload, int readTimeoutSeconds) throws IOException { + public static HttpResult post(URI uri, IAuthInfo auth, JSONObject payload, int readTimeoutSeconds) throws IOException { return sendRequest("POST", uri, auth, payload, DEFAULT_CONNECT_TIMEOUT_MS, readTimeoutSeconds * 1000); } - public static HttpResult post(URI uri, AuthToken auth) throws IOException { + public static HttpResult post(URI uri, IAuthInfo auth) throws IOException { return sendRequest("POST", uri, auth, null); } - public static HttpResult put(URI uri, AuthToken auth) throws IOException { + public static HttpResult put(URI uri, IAuthInfo auth) throws IOException { return sendRequest("PUT", uri, auth, null); } - public static HttpResult put(URI uri, AuthToken auth, JSONObject payload) throws IOException { + public static HttpResult put(URI uri, IAuthInfo auth, JSONObject payload) throws IOException { return sendRequest("PUT", uri, auth, payload, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS); } - public static HttpResult put(URI uri, AuthToken auth, JSONObject payload, int readTimoutSeconds) throws IOException { + public static HttpResult put(URI uri, IAuthInfo auth, JSONObject payload, int readTimoutSeconds) throws IOException { return sendRequest("PUT", uri, auth, payload, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS); } - public static HttpResult head(URI uri, AuthToken auth) throws IOException { + public static HttpResult head(URI uri, IAuthInfo auth) throws IOException { return sendRequest("HEAD", uri, auth, null); } - public static HttpResult delete(URI uri, AuthToken auth) throws IOException { + public static HttpResult delete(URI uri, IAuthInfo auth) throws IOException { return delete(uri, auth, null); } - public static HttpResult delete(URI uri, AuthToken auth, JSONObject payload) throws IOException { + public static HttpResult delete(URI uri, IAuthInfo auth, JSONObject payload) throws IOException { return sendRequest("DELETE", uri, auth, payload); } - public static HttpResult sendRequest(String method, URI uri, AuthToken auth, JSONObject payload) throws IOException { + public static HttpResult sendRequest(String method, URI uri, IAuthInfo auth, JSONObject payload) throws IOException { return sendRequest(method, uri, auth, payload, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS); } - public static HttpResult sendRequest(String method, URI uri, AuthToken auth, JSONObject payload, int connectTimeoutMS, int readTimeoutMS) throws IOException { + public static HttpResult sendRequest(String method, URI uri, IAuthInfo auth, JSONObject payload, int connectTimeoutMS, int readTimeoutMS) throws IOException { HttpURLConnection connection = null; if (payload != null) { Logger.log("Making a " + method + " request on " + uri + " with payload: " + payload.toString()); @@ -222,11 +221,11 @@ public static HttpResult sendRequest(String method, URI uri, AuthToken auth, JSO } } - private static void addAuthorization(HttpURLConnection connection, AuthToken auth) { - if (sslContext == null || auth == null || auth.getToken() == null || auth.getTokenType() == null || !(connection instanceof HttpsURLConnection)) { + private static void addAuthorization(HttpURLConnection connection, IAuthInfo auth) { + if (sslContext == null || auth == null || !auth.isValid() || !(connection instanceof HttpsURLConnection)) { return; } - connection.setRequestProperty("Authorization", auth.getTokenType() + " " + auth.getToken()); + connection.setRequestProperty("Authorization", auth.getHttpAuthorization()); ((HttpsURLConnection)connection).setSSLSocketFactory(sslContext.getSocketFactory()); ((HttpsURLConnection)connection).setHostnameVerifier(hostnameVerifier); } diff --git a/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/IAuthInfo.java b/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/IAuthInfo.java new file mode 100644 index 000000000..d681e4141 --- /dev/null +++ b/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/IAuthInfo.java @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + + +package org.eclipse.codewind.core.internal; + +public interface IAuthInfo { + + public boolean isValid(); + + public String getHttpAuthorization(); + +} diff --git a/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/cli/AuthToken.java b/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/cli/AuthToken.java index 1ad57b272..af7183571 100644 --- a/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/cli/AuthToken.java +++ b/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/cli/AuthToken.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019 IBM Corporation and others. + * Copyright (c) 2019, 2020 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -11,10 +11,11 @@ package org.eclipse.codewind.core.internal.cli; +import org.eclipse.codewind.core.internal.IAuthInfo; import org.eclipse.codewind.core.internal.connection.JSONObjectResult; import org.json.JSONObject; -public class AuthToken extends JSONObjectResult { +public class AuthToken extends JSONObjectResult implements IAuthInfo { private static final String ACCESS_TOKEN_KEY = "access_token"; private static final String TOKEN_TYPE_KEY = "token_type"; @@ -59,4 +60,14 @@ public boolean recentlyCreated() { return (createTimeMillis + CREATE_BUFFER) > System.currentTimeMillis(); } + @Override + public boolean isValid() { + return getToken() != null && getTokenType() != null; + } + + @Override + public String getHttpAuthorization() { + return getTokenType() + " " + getToken(); + } + } diff --git a/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/cli/CLIUtil.java b/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/cli/CLIUtil.java index 954972396..0b0aacb4b 100644 --- a/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/cli/CLIUtil.java +++ b/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/cli/CLIUtil.java @@ -204,7 +204,7 @@ public static void checkResult(String[] command, ProcessResult result, boolean c if (obj.has(ERROR_KEY) && obj.has(ERROR_DESCRIPTION_KEY)) { String msg = String.format("The cwctl '%s' command failed with error: %s", CoreUtil.formatString(command, " "), obj.getString(ERROR_DESCRIPTION_KEY)); //$NON-NLS-1$ Logger.logError(msg); - throw new IOException(obj.getString(ERROR_DESCRIPTION_KEY)); + throw new CLIException(obj.getString(ERROR_KEY), obj.getString(ERROR_DESCRIPTION_KEY)); } } } catch (JSONException e) { @@ -248,4 +248,17 @@ public static void removeCWCTL(String version) throws IOException { Logger.log("Removing folder: " + cwctlFolder.getAbsolutePath()); FileUtil.deleteDirectory(cwctlFolder.getAbsolutePath(), true); } + + @SuppressWarnings("serial") + public static class CLIException extends IOException { + + public final String errorId; + public final String errorMsg; + + public CLIException(String errorId, String errorMsg) { + super(errorMsg); + this.errorId = errorId; + this.errorMsg = errorMsg; + } + } } diff --git a/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/cli/TemplateUtil.java b/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/cli/TemplateUtil.java index fdf8fc73a..24f412e4d 100644 --- a/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/cli/TemplateUtil.java +++ b/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/cli/TemplateUtil.java @@ -41,6 +41,9 @@ public class TemplateUtil { private static final String URL_OPTION = "--url"; private static final String NAME_OPTION = "--name"; private static final String DESCRIPTION_OPTION = "--description"; + private static final String USERNAME_OPTION = "--username"; + private static final String PASSWORD_OPTION = "--password"; + private static final String PERSONAL_ACCESS_TOKEN_OPTION = "--personalAccessToken"; public static List listTemplates(boolean enabledOnly, String conid, IProgressMonitor monitor) throws IOException, JSONException, TimeoutException { SubMonitor mon = SubMonitor.convert(monitor, 100); @@ -83,8 +86,26 @@ public static List listTemplateSources(String conid, IProgressMo } } - public static void addTemplateSource(String url, String name, String description, String conid, IProgressMonitor monitor) throws IOException, JSONException, TimeoutException { - runTemplateSourceCmd(REPO_ADD_CMD, new String[] {URL_OPTION, url, NAME_OPTION, name, DESCRIPTION_OPTION, description, CLIUtil.CON_ID_OPTION, conid}, null, monitor); + public static void addTemplateSource(String url, String username, String password, String accessToken, String name, String description, String conid, IProgressMonitor monitor) throws IOException, JSONException, TimeoutException { + List options = new ArrayList(); + options.add(URL_OPTION); + options.add(url); + if (username != null && password != null) { + options.add(USERNAME_OPTION); + options.add(username); + options.add(PASSWORD_OPTION); + options.add(password); + } else if (accessToken != null) { + options.add(PERSONAL_ACCESS_TOKEN_OPTION); + options.add(accessToken); + } + options.add(NAME_OPTION); + options.add(name); + options.add(DESCRIPTION_OPTION); + options.add(description); + options.add(CLIUtil.CON_ID_OPTION); + options.add(conid); + runTemplateSourceCmd(REPO_ADD_CMD, options.toArray(new String[options.size()]), null, monitor); } public static void removeTemplateSource(String url, String conid, IProgressMonitor monitor) throws IOException, JSONException, TimeoutException { diff --git a/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/connection/JSONObjectResult.java b/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/connection/JSONObjectResult.java index e812c1e9f..c2a1c6c94 100644 --- a/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/connection/JSONObjectResult.java +++ b/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/connection/JSONObjectResult.java @@ -19,7 +19,7 @@ import org.json.JSONException; import org.json.JSONObject; -public abstract class JSONObjectResult { +public class JSONObjectResult { protected final JSONObject result; protected final String type; diff --git a/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/connection/RepositoryInfo.java b/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/connection/RepositoryInfo.java index 45b2ff6d8..70657ab4d 100644 --- a/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/connection/RepositoryInfo.java +++ b/dev/org.eclipse.codewind.core/src/org/eclipse/codewind/core/internal/connection/RepositoryInfo.java @@ -23,6 +23,8 @@ public class RepositoryInfo extends JSONObjectResult { public static final String ENABLED_KEY = "enabled"; public static final String PROTECTED_KEY = "protected"; public static final String STYLES_KEY = "projectStyles"; + public static final String AUTHENTICATION_KEY = "authentication"; + public static final String USERNAME_KEY = "username"; public RepositoryInfo(JSONObject repo) { super(repo, "repository"); @@ -51,4 +53,17 @@ public boolean isProtected() { public List getStyles() { return getStringArray(STYLES_KEY); } + + public boolean hasAuthentication() { + return hasKey(AUTHENTICATION_KEY); + } + + public String getUsername() { + JSONObject obj = hasAuthentication() ? getObject(AUTHENTICATION_KEY) : null; + if (obj != null) { + JSONObjectResult authObj = new JSONObjectResult(obj, "template source auth"); + return authObj.getString(USERNAME_KEY); + } + return null; + } } diff --git a/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/IDEUtil.java b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/IDEUtil.java index fc933dae7..e9faa365b 100644 --- a/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/IDEUtil.java +++ b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/IDEUtil.java @@ -37,6 +37,7 @@ import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.browser.IWebBrowser; import org.eclipse.ui.browser.IWorkbenchBrowserSupport; @@ -164,4 +165,8 @@ public static Optional getConsoleForLaunch(ILaunch launch) { && processes.contains(((org.eclipse.debug.ui.console.IConsole) console).getProcess())) .findFirst(); } + + public static String getTextValue(Text text) { + return text.getText() == null || text.getText().trim().isEmpty() ? null : text.getText().trim(); + } } diff --git a/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/messages/Messages.java b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/messages/Messages.java index a25b34a88..ca5ce627e 100644 --- a/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/messages/Messages.java +++ b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/messages/Messages.java @@ -499,6 +499,7 @@ public class Messages extends NLS { public static String RepoMgmtDescription; public static String RepoMgmtLearnMoreLink; public static String RepoMgmtAddButton; + public static String RepoMgmtEditButton; public static String RepoMgmtRemoveButton; public static String RepoMgmtDescriptionLabel; public static String RepoMgmtStylesLabel; @@ -510,17 +511,46 @@ public class Messages extends NLS { public static String AddRepoDialogShell; public static String AddRepoDialogTitle; - public static String AddRepoDialogMessage; + public static String EditRepoDialogShell; + public static String EditRepoDialogTitle; + public static String AddRepoURLPageMessage; + public static String AddRepoAuthPageMessage; + public static String AddRepoDetailsPageMessage; public static String AddRepoDialogNameLabel; public static String AddRepoDialogDescriptionLabel; public static String AddRepoDialogUrlLabel; - public static String AddRepoDialogAutoFillButtonLabel; - public static String AddRepoDialogAutoFillButtonTooltip; + public static String AddRepoDialogInvalidUrlTitle; + public static String AddRepoDialogInvalidUrlError; + public static String AddRepoDialogInvalidUrlMsg; + public static String AddRepoDialogUrlPingTask; + public static String AddRepoDialogUrlPingFailedDefaultMsg; + public static String AddRepoDialogUrlPingFailedTitle; + public static String AddRepoDialogUrlPingFailedError; + public static String AddRepoDialogUrlPingFailedMsg; + public static String AddRepoDialogAuthRequiredCheckboxLabel; + public static String AddRepoDialogAuthRequiredCheckboxTooltip; + public static String AddRepoDialogLogonAuthButton; + public static String AddRepoDialogAccessTokenAuthButton; + public static String AddRepoDialogUsernameLabel; + public static String AddRepoDialogPasswordLabel; + public static String AddRepoDialogAccessTokenLabel; + public static String AddRepoDialogAuthTestButtonLabel; + public static String AddRepoDialogAuthTestTaskLabel; + public static String AddRepoDialogAuthTestFailedTitle; + public static String AddRepoDialogAuthTestFailedDefaultMsg; + public static String AddRepoDialogAuthTestFailedError; + public static String AddRepoDialogAuthTestSuccessMsg; + public static String AddRepoDialogAuthTestFailedMsg; + public static String AddRepoDialogResetButtonLabel; + public static String AddRepoDialogResetButtonTooltip; public static String AddRepoDialogAutoFillTaskLabel; public static String AddRepoDialogAutoFillNotAvailableMsg; public static String AddRepoDialogNoName; public static String AddRepoDialogNoDescription; public static String AddRepoDialogNoUrl; + public static String AddRepoDialogNoUsername; + public static String AddRepoDialogNoPassword; + public static String AddRepoDialogNoAccessToken; public static String RepoListTask; public static String RepoUpdateTask; diff --git a/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/messages/messages.properties b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/messages/messages.properties index 3bd3843a8..2709a66b8 100644 --- a/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/messages/messages.properties +++ b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/messages/messages.properties @@ -493,6 +493,7 @@ RepoMgmtDialogMessage=Add, remove, enable and disable template sources RepoMgmtDescription=Enable or disable template sources using the check boxes. Use the buttons to add and remove template sources. Default template sources cannot be removed. RepoMgmtLearnMoreLink=Learn more... RepoMgmtAddButton=&Add... +RepoMgmtEditButton=&Edit... RepoMgmtRemoveButton=&Remove RepoMgmtDescriptionLabel=Description: RepoMgmtStylesLabel=Styles: @@ -504,17 +505,46 @@ RepoMgmtAddFailed=An error occurred trying to add template source: {0} AddRepoDialogShell=Add Template Source AddRepoDialogTitle=Add a Template Source -AddRepoDialogMessage=Supply a URL, name, and description for the new template source +EditRepoDialogShell=Edit Template Source +EditRepoDialogTitle=Edit a Template Source +AddRepoURLPageMessage=Enter the URL of the template source and indicate if the URL requires authentication. +AddRepoAuthPageMessage=Provide authorization details for the template source. +AddRepoDetailsPageMessage=Provide the name and description for the template source. AddRepoDialogNameLabel=&Name: AddRepoDialogDescriptionLabel=&Description: AddRepoDialogUrlLabel=&URL: -AddRepoDialogAutoFillButtonLabel=Auto &Fill from URL -AddRepoDialogAutoFillButtonTooltip=Fills in the name and description if available from the URL location +AddRepoDialogInvalidUrlTitle=Invalid URL +AddRepoDialogInvalidUrlError=The specified URL is not valid:\n\n{0} +AddRepoDialogInvalidUrlMsg=The specified URL is not valid. +AddRepoDialogUrlPingTask=Pinging: {0} +AddRepoDialogUrlPingFailedDefaultMsg=The ping failed with error code: {0} +AddRepoDialogUrlPingFailedTitle=Ping Error +AddRepoDialogUrlPingFailedError=The following error occurred while trying to ping URL: {0}\n\n{1} +AddRepoDialogUrlPingFailedMsg=Ping of the URL was unsuccessful. +AddRepoDialogAuthRequiredCheckboxLabel=&Authentication required for this URL +AddRepoDialogAuthRequiredCheckboxTooltip=Select if authentication is required for this URL. +AddRepoDialogLogonAuthButton=&Logon authentication +AddRepoDialogAccessTokenAuthButton=&Access token authentication +AddRepoDialogUsernameLabel=&Username: +AddRepoDialogPasswordLabel=&Password: +AddRepoDialogAccessTokenLabel=Access &token: +AddRepoDialogAuthTestButtonLabel=Te&st Authentication +AddRepoDialogAuthTestTaskLabel=Testing authentication for: {0} +AddRepoDialogAuthTestFailedTitle=Authentication Test +AddRepoDialogAuthTestFailedDefaultMsg=The authentication test failed with error code: {0} +AddRepoDialogAuthTestFailedError=The following error occurred while testing authentication for URL: {0}\n\n{1} +AddRepoDialogAuthTestSuccessMsg=The authentication test was successful. +AddRepoDialogAuthTestFailedMsg=The authentication test was not successful. +AddRepoDialogResetButtonLabel=&Restore Defaults +AddRepoDialogResetButtonTooltip=Reset to the defaults provided by the URL AddRepoDialogAutoFillTaskLabel=Retrieving template source details -AddRepoDialogAutoFillNotAvailableMsg=The template source details could not be retrieved from the URL location -AddRepoDialogNoName=Fill in a name for the template source -AddRepoDialogNoDescription=Fill in a description for the template source -AddRepoDialogNoUrl=Fill in a URL for the template source +AddRepoDialogAutoFillNotAvailableMsg=The template source details could not be retrieved from the URL location. +AddRepoDialogNoName=Fill in a name for the template source. +AddRepoDialogNoDescription=Fill in a description for the template source. +AddRepoDialogNoUrl=Fill in a URL for the template source. +AddRepoDialogNoUsername=Fill in a username for template source authorization. +AddRepoDialogNoPassword=Fill in a password for template source authorization. +AddRepoDialogNoAccessToken=Fill in an access token for template source authorization. RepoListTask=Retrieving template sources for: {0} RepoUpdateTask=Updating template sources diff --git a/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/AddTemplateSourceWizard.java b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/AddTemplateSourceWizard.java new file mode 100644 index 000000000..12a58bdf3 --- /dev/null +++ b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/AddTemplateSourceWizard.java @@ -0,0 +1,160 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.codewind.ui.internal.prefs; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.URI; +import java.net.URISyntaxException; + +import org.eclipse.codewind.core.internal.HttpUtil; +import org.eclipse.codewind.core.internal.HttpUtil.HttpResult; +import org.eclipse.codewind.ui.internal.IDEUtil; +import org.eclipse.codewind.ui.internal.messages.Messages; +import org.eclipse.codewind.ui.internal.prefs.RepositoryManagementComposite.RepoEntry; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.osgi.util.NLS; +import org.eclipse.ui.INewWizard; +import org.eclipse.ui.IWorkbench; + +public class AddTemplateSourceWizard extends Wizard implements INewWizard { + + protected TemplateSourceURLPage urlPage; + protected TemplateSourceAuthPage authPage; + protected TemplateSourceDetailsPage detailsPage; + private boolean authPageIncluded = false; + + public AddTemplateSourceWizard() { + setNeedsProgressMonitor(true); + } + + @Override + public void init(IWorkbench workbench, IStructuredSelection selection) { + // Empty + } + + @Override + public void addPages() { + setWindowTitle(Messages.AddRepoDialogShell); + urlPage = new TemplateSourceURLPage(Messages.AddRepoDialogShell, Messages.AddRepoDialogTitle); + addPage(urlPage); + authPage = new TemplateSourceAuthPage(Messages.AddRepoDialogShell, Messages.AddRepoDialogTitle); + addPage(authPage); + detailsPage = new TemplateSourceDetailsPage(Messages.AddRepoDialogShell, Messages.AddRepoDialogTitle); + addPage(detailsPage); + } + + @Override + public IWizardPage getNextPage(IWizardPage page) { + if (page == urlPage) { + // Check that the URL is valid + URI uri; + try { + uri = new URI(urlPage.getTemplateSourceUrl()); + } catch (URISyntaxException e) { + IDEUtil.openInfoDialog(Messages.AddRepoDialogInvalidUrlTitle, NLS.bind(Messages.AddRepoDialogInvalidUrlError, e.toString())); + urlPage.setErrorMessage(Messages.AddRepoDialogInvalidUrlMsg); + return urlPage; + } + + // Check if the authentication required button was selected and return the auth page + if (urlPage.getAuthRequired()) { + authPageIncluded = true; + authPage.updatePage(uri); + return authPage; + } + + // Check if authentication is required by the URL and return the auth page + try { + Boolean[] requiresAuth = new Boolean[1]; + requiresAuth[0] = Boolean.FALSE; + IRunnableWithProgress runnable = new IRunnableWithProgress() { + @Override + public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { + try { + SubMonitor.convert(monitor, NLS.bind(Messages.AddRepoDialogUrlPingTask, uri), IProgressMonitor.UNKNOWN); + HttpResult result = HttpUtil.get(uri, null); + if (!result.isGoodResponse) { + if (result.responseCode == 401) { + requiresAuth[0] = Boolean.TRUE; + } else { + String errorMsg = result.error; + if (errorMsg == null || errorMsg.trim().isEmpty()) { + errorMsg = NLS.bind(Messages.AddRepoDialogUrlPingFailedDefaultMsg, result.responseCode); + } + throw new InvocationTargetException(new IOException(errorMsg)); + } + } + } catch (IOException e) { + throw new InvocationTargetException(e, e.toString()); + } + } + }; + getContainer().run(true, true, runnable); + if (requiresAuth[0].booleanValue()) { + authPageIncluded = true; + authPage.updatePage(uri); + return authPage; + } + } catch (Exception e) { + String msg = e instanceof InvocationTargetException ? ((InvocationTargetException)e).getCause().toString() : e.toString(); + IDEUtil.openInfoDialog(Messages.AddRepoDialogUrlPingFailedTitle, NLS.bind(Messages.AddRepoDialogUrlPingFailedError, urlPage.getTemplateSourceUrl(), msg)); + urlPage.setErrorMessage(Messages.AddRepoDialogUrlPingFailedMsg); + return urlPage; + } + + // If no authentication required, return the details page + authPageIncluded = false; + detailsPage.updatePage(urlPage.getTemplateSourceUrl(), null); + return detailsPage; + } else if (page == authPage) { + detailsPage.updatePage(urlPage.getTemplateSourceUrl(), authPage.getAuthInfo()); + return detailsPage; + } + return super.getNextPage(page); + } + + @Override + public boolean canFinish() { + return urlPage.canFinish() && (!authPageIncluded || authPage.canFinish()) && detailsPage.canFinish(); + } + + @Override + public boolean performCancel() { + return true; + } + + @Override + public boolean performFinish() { + if (!canFinish()) { + return false; + } + return true; + } + + RepoEntry getRepoEntry() { + String url = urlPage.getTemplateSourceUrl(); + String name = detailsPage.getTemplateSourceName(); + String description = detailsPage.getTemplateSourceDescription(); + if (name != null && !name.isEmpty() && + description != null && !description.isEmpty() && + url != null && !url.isEmpty()) { + return new RepoEntry(url, authPage.getUsername(), authPage.getPassword(), authPage.getToken(), name, description); + } + return null; + } +} diff --git a/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/EditTemplateSourceWizard.java b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/EditTemplateSourceWizard.java new file mode 100644 index 000000000..288347a1c --- /dev/null +++ b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/EditTemplateSourceWizard.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.codewind.ui.internal.prefs; + +import org.eclipse.codewind.ui.internal.messages.Messages; +import org.eclipse.codewind.ui.internal.prefs.RepositoryManagementComposite.RepoEntry; + +public class EditTemplateSourceWizard extends AddTemplateSourceWizard { + + private final RepoEntry repo; + + public EditTemplateSourceWizard(RepoEntry repo) { + super(); + this.repo = repo; + } + + @Override + public void addPages() { + setWindowTitle(Messages.EditRepoDialogShell); + urlPage = new TemplateSourceURLPage(Messages.EditRepoDialogShell, Messages.EditRepoDialogTitle, repo.url); + addPage(urlPage); + authPage = new TemplateSourceAuthPage(Messages.EditRepoDialogShell, Messages.EditRepoDialogTitle, repo.accessToken == null, repo.username); + addPage(authPage); + detailsPage = new TemplateSourceDetailsPage(Messages.EditRepoDialogShell, Messages.EditRepoDialogTitle, repo.name, repo.description); + addPage(detailsPage); + } +} diff --git a/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/RepositoryManagementComposite.java b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/RepositoryManagementComposite.java index 1ab537872..2fcea682d 100644 --- a/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/RepositoryManagementComposite.java +++ b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/RepositoryManagementComposite.java @@ -11,7 +11,6 @@ package org.eclipse.codewind.ui.internal.prefs; -import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; @@ -19,8 +18,6 @@ import java.util.Optional; import org.eclipse.codewind.core.CodewindCorePlugin; -import org.eclipse.codewind.core.internal.HttpUtil; -import org.eclipse.codewind.core.internal.HttpUtil.HttpResult; import org.eclipse.codewind.core.internal.Logger; import org.eclipse.codewind.core.internal.cli.TemplateUtil; import org.eclipse.codewind.core.internal.connection.CodewindConnection; @@ -34,21 +31,15 @@ import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubMonitor; -import org.eclipse.jface.dialogs.IDialogConstants; -import org.eclipse.jface.dialogs.TitleAreaDialog; -import org.eclipse.jface.operation.IRunnableWithProgress; -import org.eclipse.jface.operation.ModalContext; import org.eclipse.jface.viewers.CheckStateChangedEvent; import org.eclipse.jface.viewers.CheckboxTableViewer; import org.eclipse.jface.viewers.ICheckStateListener; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.LabelProvider; -import org.eclipse.jface.wizard.ProgressMonitorPart; +import org.eclipse.jface.wizard.WizardDialog; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.ScrolledComposite; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Font; @@ -60,17 +51,14 @@ import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Link; import org.eclipse.swt.widgets.Listener; -import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.TableItem; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.browser.IWebBrowser; import org.eclipse.ui.browser.IWorkbenchBrowserSupport; -import org.json.JSONObject; public class RepositoryManagementComposite extends Composite { @@ -78,7 +66,7 @@ public class RepositoryManagementComposite extends Composite { private final List repoList; // Original set of repos private List repoEntries; // Current set of repos (content of the table) private CheckboxTableViewer repoViewer; - private Button removeButton; + private Button editButton, removeButton; private Font boldFont; private Label descLabel; private Text descText; @@ -134,7 +122,7 @@ public void widgetSelected(SelectionEvent event) { repoViewer.setContentProvider(new RepoContentProvider()); repoViewer.setLabelProvider(new RepoLabelProvider()); repoViewer.setInput(repoEntries.toArray(new RepoEntry[repoEntries.size()])); - GridData tableData = new GridData(GridData.FILL, GridData.FILL, true, true, 1, 2); + GridData tableData = new GridData(GridData.FILL, GridData.FILL, true, true, 1, 3); tableData.horizontalIndent = 1; repoViewer.getTable().setLayoutData(tableData); @@ -142,6 +130,10 @@ public void widgetSelected(SelectionEvent event) { addButton.setText(Messages.RepoMgmtAddButton); addButton.setLayoutData(new GridData(GridData.FILL, GridData.BEGINNING, false, false)); + editButton = new Button(this, SWT.PUSH); + editButton.setText(Messages.RepoMgmtEditButton); + editButton.setLayoutData(new GridData(GridData.FILL, GridData.BEGINNING, false, false)); + removeButton = new Button(this, SWT.PUSH); removeButton.setText(Messages.RepoMgmtRemoveButton); removeButton.setLayoutData(new GridData(GridData.FILL, GridData.BEGINNING, false, false)); @@ -157,9 +149,11 @@ public void handleEvent(Event event) { addButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { - AddDialog dialog = new AddDialog(getShell()); + AddTemplateSourceWizard wizard = new AddTemplateSourceWizard(); + WizardDialog dialog = new WizardDialog(Display.getDefault().getActiveShell(), wizard); + dialog.create(); if (dialog.open() == IStatus.OK) { - RepoEntry repoEntry = dialog.getRepoEntry(); + RepoEntry repoEntry = wizard.getRepoEntry(); if (repoEntry != null) { repoEntries.add(repoEntry); repoViewer.refresh(); @@ -169,6 +163,27 @@ public void widgetSelected(SelectionEvent event) { } }); + editButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + TableItem[] items = repoViewer.getTable().getSelection(); + RepoEntry repoEntry = (RepoEntry) items[0].getData(); + EditTemplateSourceWizard wizard = new EditTemplateSourceWizard(repoEntry); + WizardDialog dialog = new WizardDialog(Display.getDefault().getActiveShell(), wizard); + dialog.create(); + if (dialog.open() == IStatus.OK) { + RepoEntry newRepoEntry = wizard.getRepoEntry(); + if (newRepoEntry != null) { + int index = repoEntries.indexOf(repoEntry); + newRepoEntry.enabled = repoEntry.enabled; + repoEntries.set(index, newRepoEntry); + repoViewer.refresh(); + repoViewer.setChecked(newRepoEntry, newRepoEntry.enabled); + } + } + } + }); + removeButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { @@ -344,6 +359,8 @@ private void updateButtons() { enabled = false; } removeButton.setEnabled(enabled); + + editButton.setEnabled(items.length == 1 && enabled); } private class RepoContentProvider implements IStructuredContentProvider { @@ -409,7 +426,7 @@ public IStatus updateRepos(IProgressMonitor monitor) { if (entry.info == null) { // Add the repository try { - TemplateUtil.addTemplateSource(entry.url, entry.name, entry.description, connection.getConid(), mon.split(25)); + TemplateUtil.addTemplateSource(entry.url, entry.username, entry.password, entry.accessToken, entry.name, entry.description, connection.getConid(), mon.split(25)); } catch (Exception e) { Logger.logError("Failed to add repository: " + entry.url, e); //$NON-NLS-1$ multiStatus.add(new Status(IStatus.ERROR, CodewindCorePlugin.PLUGIN_ID, NLS.bind(Messages.RepoMgmtAddFailed, entry.url), e)); @@ -452,24 +469,37 @@ private Optional getRepoEntry(RepositoryInfo info) { return repoEntries.stream().filter(entry -> entry.info == info).findFirst(); } - private static class RepoEntry { + public static class RepoEntry { + public final String url; + public final String username; + public final String password; + public final String accessToken; public final String name; public final String description; - public final String url; public boolean enabled; public RepositoryInfo info; - public RepoEntry(String name, String description, String url) { + public RepoEntry(String url, String name, String description) { + this(url, null, null, null, name, description); + } + + public RepoEntry(String url, String username, String password, String accessToken, String name, String description) { + this.url = url; + this.username = username; + this.password = password; + this.accessToken = accessToken; this.name = name; this.description = description; - this.url = url; this.enabled = true; } public RepoEntry(RepositoryInfo info) { + this.url = info.getURL(); + this.username = null; + this.password = null; + this.accessToken = null; this.name = info.getName(); this.description = info.getDescription(); - this.url = info.getURL(); this.enabled = info.getEnabled(); this.info = info; } @@ -502,223 +532,4 @@ public String getStyles() { return Messages.GenericNotAvailable; } } - - private static class AddDialog extends TitleAreaDialog { - - public static final String DETAILS_FILE_NAME = "templates.json"; //$NON-NLS-1$ - public static final String NAME_KEY = "name"; //$NON-NLS-1$ - public static final String DESCRIPTION_KEY = "description"; //$NON-NLS-1$ - - private String name; - private String description; - private String url; - private ProgressMonitorPart progressMon; - - public AddDialog(Shell parentShell) { - super(parentShell); - } - - @Override - protected void configureShell(Shell newShell) { - super.configureShell(newShell); - newShell.setText(Messages.AddRepoDialogShell); - } - - @Override - protected boolean isResizable() { - return true; - } - - @Override - protected Control createButtonBar(Composite parent) { - return super.createButtonBar(parent); - } - - protected Control createDialogArea(Composite parent) { - setTitleImage(CodewindUIPlugin.getImage(CodewindUIPlugin.CODEWIND_BANNER)); - setTitle(Messages.AddRepoDialogTitle); - setMessage(Messages.AddRepoDialogMessage); - - final Composite composite = new Composite(parent, SWT.NONE); - GridLayout layout = new GridLayout(); - layout.marginHeight = 11; - layout.marginWidth = 9; - layout.horizontalSpacing = 5; - layout.verticalSpacing = 20; - layout.numColumns = 2; - composite.setLayout(layout); - GridData data = new GridData(GridData.FILL_BOTH); - data.minimumWidth = 300; - composite.setLayoutData(data); - composite.setFont(parent.getFont()); - - Label label = new Label(composite, SWT.NONE); - label.setText(Messages.AddRepoDialogUrlLabel); - label.setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER, false, false)); - - Text urlText = new Text(composite, SWT.BORDER); - urlText.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false)); - - Group infoGroup = new Group(composite, SWT.NONE); - infoGroup.setText("Template source details"); - layout = new GridLayout(); - layout.marginHeight = 11; - layout.marginWidth = 9; - layout.horizontalSpacing = 5; - layout.verticalSpacing = 7; - layout.numColumns = 2; - infoGroup.setLayout(layout); - infoGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1)); - - label = new Label(infoGroup, SWT.NONE); - label.setText(Messages.AddRepoDialogNameLabel); - label.setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER, false, false)); - - Text nameText = new Text(infoGroup, SWT.BORDER); - nameText.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false)); - - label = new Label(infoGroup, SWT.NONE); - label.setText(Messages.AddRepoDialogDescriptionLabel); - label.setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER, false, false)); - - Text descriptionText = new Text(infoGroup, SWT.BORDER); - descriptionText.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false)); - - // Button to auto fill the name and description fields for the template source - Button autoFillButton = new Button(infoGroup, SWT.PUSH); - autoFillButton.setText(Messages.AddRepoDialogAutoFillButtonLabel); - autoFillButton.setToolTipText(Messages.AddRepoDialogAutoFillButtonTooltip); - autoFillButton.setLayoutData(new GridData(GridData.END, GridData.FILL, false, false, 2, 1)); - - // Progress monitor widget - progressMon = new ProgressMonitorPart(parent, layout, true); - progressMon.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - progressMon.setVisible(false); - - urlText.addModifyListener(new ModifyListener() { - @Override - public void modifyText(ModifyEvent e) { - url = urlText.getText().trim(); - enableOKButton(validate()); - autoFillButton.setEnabled(url != null && !url.isEmpty()); - } - }); - - nameText.addModifyListener(new ModifyListener() { - @Override - public void modifyText(ModifyEvent e) { - name = nameText.getText().trim(); - enableOKButton(validate()); - } - }); - - descriptionText.addModifyListener(new ModifyListener() { - @Override - public void modifyText(ModifyEvent e) { - description = descriptionText.getText().trim(); - enableOKButton(validate()); - } - }); - - autoFillButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent event) { - try { - runWithProgress((IProgressMonitor monitor) -> { - try { - monitor.beginTask(Messages.AddRepoDialogAutoFillTaskLabel, IProgressMonitor.UNKNOWN); - - // Construct the URL for the details file from the template source URL - URL repoUrl = new URL(url); - String path = repoUrl.getPath(); - path = path.substring(0, path.lastIndexOf("/") + 1) + DETAILS_FILE_NAME; - URL detailsUrl = new URL(repoUrl.getProtocol(), repoUrl.getHost(), path); - - // Try to get the template source details - HttpResult result = HttpUtil.get(detailsUrl.toURI()); - if (result.isGoodResponse && result.response != null && !result.response.isEmpty()) { - JSONObject jsonObj = new JSONObject(result.response); - String name = jsonObj.has(NAME_KEY) ? jsonObj.getString(NAME_KEY) : null; - String description = jsonObj.has(DESCRIPTION_KEY) ? jsonObj.getString(DESCRIPTION_KEY) : null; - Display.getDefault().syncExec(() -> { - // The name should at least be set - if (name == null || name.isEmpty()) { - Logger.logError("Found the template source information but the name is null or empty: " + detailsUrl); - AddDialog.this.setErrorMessage(Messages.AddRepoDialogAutoFillNotAvailableMsg); - } else { - nameText.setText(name); - descriptionText.setText(description == null ? "" : description); - } - }); - } else { - // Don't log this as an error as the template source may not provide details - Logger.log("Got error code " + result.error + " trying to retrieve the template source details for url: " + detailsUrl + ", and error: " + result.error); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - Display.getDefault().syncExec(() -> AddDialog.this.setErrorMessage(Messages.AddRepoDialogAutoFillNotAvailableMsg)); - } - } catch (Exception e) { - Logger.logError("An error occurred trying to retrieve the template source details for URL: " + url, e); //$NON-NLS-1$ - Display.getDefault().syncExec(() -> AddDialog.this.setErrorMessage(Messages.AddRepoDialogAutoFillNotAvailableMsg)); - } finally { - monitor.done(); - } - }); - } catch (Exception e) { - Logger.logError("An error occurred trying to get the template source details", e); //$NON-NLS-1$ - } - } - }); - - autoFillButton.setEnabled(false); - - return composite; - } - - @Override - protected void createButtonsForButtonBar(Composite parent) { - super.createButtonsForButtonBar(parent); - enableOKButton(false); - } - - protected void enableOKButton(boolean value) { - getButton(IDialogConstants.OK_ID).setEnabled(value); - } - - private boolean validate() { - if (url == null || url.isEmpty()) { - setErrorMessage(Messages.AddRepoDialogNoUrl); - return false; - } - if (name == null || name.isEmpty()) { - setErrorMessage(Messages.AddRepoDialogNoName); - return false; - } - if (description == null || description.isEmpty()) { - setErrorMessage(Messages.AddRepoDialogNoDescription); - return false; - } - - setErrorMessage(null); - return true; - } - - public RepoEntry getRepoEntry() { - if (name != null && !name.isEmpty() && - description != null && !description.isEmpty() && - url != null && !url.isEmpty()) { - return new RepoEntry(name, description, url); - } - return null; - } - - public void runWithProgress(IRunnableWithProgress runnable) throws InvocationTargetException, InterruptedException { - progressMon.setVisible(true); - try { - ModalContext.run(runnable, true, progressMon, getShell().getDisplay()); - } finally { - progressMon.done(); - progressMon.setVisible(false); - } - } - } - } diff --git a/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/TemplateSourceAuthComposite.java b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/TemplateSourceAuthComposite.java new file mode 100644 index 000000000..bddf59e92 --- /dev/null +++ b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/TemplateSourceAuthComposite.java @@ -0,0 +1,310 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.codewind.ui.internal.prefs; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.InvocationTargetException; +import java.net.URI; +import java.util.Arrays; +import java.util.Base64; + +import org.eclipse.codewind.core.internal.HttpUtil; +import org.eclipse.codewind.core.internal.HttpUtil.HttpResult; +import org.eclipse.codewind.core.internal.IAuthInfo; +import org.eclipse.codewind.core.internal.Logger; +import org.eclipse.codewind.ui.CodewindUIPlugin; +import org.eclipse.codewind.ui.internal.IDEUtil; +import org.eclipse.codewind.ui.internal.messages.Messages; +import org.eclipse.codewind.ui.internal.wizards.CompositeContainer; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.PlatformUI; + +public class TemplateSourceAuthComposite extends Composite { + + private CompositeContainer container; + private Button logonButton, tokenButton, testButton; + private boolean isLogonMethod; + private Composite logonComposite, tokenComposite; + private Text usernameText, passwordText, tokenText; + private String usernameValue, passwordValue, tokenValue; + private URI uri; + + protected TemplateSourceAuthComposite(Composite parent, CompositeContainer container, boolean isLogonMethod, String username) { + super(parent, SWT.NONE); + this.container = container; + this.isLogonMethod = isLogonMethod; + this.usernameValue = username; + createControl(); + } + + protected void createControl() { + GridLayout layout = new GridLayout(); + layout.horizontalSpacing = 5; + layout.verticalSpacing = 15; + setLayout(layout); + + Group authGroup = new Group(this, SWT.NONE); + authGroup.setText("Authentication method"); + layout = new GridLayout(); + layout.horizontalSpacing = 5; + layout.verticalSpacing = 15; + layout.marginHeight = 15; + authGroup.setLayout(layout); + authGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + // Logon button and composite + logonButton = new Button(authGroup, SWT.RADIO); + logonButton.setText(Messages.AddRepoDialogLogonAuthButton); + logonButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false)); + logonButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + setEnablement(logonButton.getSelection()); + validate(); + } + }); + + logonComposite = createSubComposite(authGroup, 2); + + Label label = new Label(logonComposite, SWT.NONE); + label.setText(Messages.AddRepoDialogUsernameLabel); + label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + + usernameText = new Text(logonComposite, SWT.BORDER); + usernameText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + usernameText.addModifyListener((event) -> validate()); + + label = new Label(logonComposite, SWT.NONE); + label.setText(Messages.AddRepoDialogPasswordLabel); + label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + + passwordText = new Text(logonComposite, SWT.BORDER | SWT.PASSWORD); + passwordText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + passwordText.addModifyListener((event) -> validate()); + + // Token button and composite + tokenButton = new Button(authGroup, SWT.RADIO); + tokenButton.setText(Messages.AddRepoDialogAccessTokenAuthButton); + tokenButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false)); + tokenButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + setEnablement(!tokenButton.getSelection()); + validate(); + } + }); + + tokenComposite = createSubComposite(authGroup, 2); + + label = new Label(tokenComposite, SWT.NONE); + label.setText(Messages.AddRepoDialogAccessTokenLabel); + label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + + tokenText = new Text(tokenComposite, SWT.BORDER | SWT.PASSWORD); + tokenText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + tokenText.addModifyListener((event) -> validate()); + + testButton = new Button(this, SWT.PUSH); + testButton.setText(Messages.AddRepoDialogAuthTestButtonLabel); + testButton.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false)); + + testButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + IRunnableWithProgress runnable = new IRunnableWithProgress() { + @Override + public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { + try { + SubMonitor.convert(monitor, NLS.bind(Messages.AddRepoDialogAuthTestTaskLabel, uri), IProgressMonitor.UNKNOWN); + HttpResult result = HttpUtil.get(uri, getAuthInfo()); + if (!result.isGoodResponse) { + String errorMsg = result.error; + if (errorMsg == null || errorMsg.trim().isEmpty()) { + errorMsg = NLS.bind(Messages.AddRepoDialogAuthTestFailedDefaultMsg, result.responseCode); + } + throw new InvocationTargetException(new IOException(errorMsg)); + } + } catch (IOException e) { + throw new InvocationTargetException(e, e.toString()); + } + } + }; + try { + container.run(runnable); + container.setErrorMessage(null); + container.setMessage(Messages.AddRepoDialogAuthTestSuccessMsg); + } catch (Exception e) { + String msg = e instanceof InvocationTargetException ? ((InvocationTargetException)e).getCause().toString() : e.toString(); + IDEUtil.openInfoDialog(Messages.AddRepoDialogAuthTestFailedTitle, NLS.bind(Messages.AddRepoDialogAuthTestFailedError, uri, msg)); + container.setErrorMessage(Messages.AddRepoDialogAuthTestFailedMsg); + } + } + }); + + testButton.setEnabled(false); + logonButton.setSelection(isLogonMethod); + tokenButton.setSelection(!isLogonMethod); + if (isLogonMethod && usernameValue != null) { + usernameText.setText(usernameValue); + } + setEnablement(isLogonMethod); + + // Add Context Sensitive Help + PlatformUI.getWorkbench().getHelpSystem().setHelp(this, CodewindUIPlugin.MAIN_CONTEXTID); + } + + protected Composite createSubComposite(Composite parent, int numColumn) { + Composite composite = new Composite(parent, SWT.NONE); + GridLayout layout = new GridLayout(); + layout.numColumns = numColumn; + layout.marginHeight = 2; + layout.marginWidth = 10; + layout.verticalSpacing = 5; + layout.horizontalSpacing = 15; + composite.setLayout(layout); + composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + return composite; + } + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + if (visible) { + if (logonButton.getSelection()) { + usernameText.setFocus(); + } else { + tokenText.setFocus(); + } + } + } + + void updatePage(URI uri) { + this.uri = uri; + } + + private void setEnablement(boolean isLogonMethod) { + Arrays.stream(logonComposite.getChildren()).forEach(c -> c.setEnabled(isLogonMethod)); + Arrays.stream(tokenComposite.getChildren()).forEach(c -> c.setEnabled(!isLogonMethod)); + } + + private void validate() { + String errorMsg = null; + isLogonMethod = logonButton.getSelection(); + if (isLogonMethod) { + usernameValue = IDEUtil.getTextValue(usernameText); + passwordValue = IDEUtil.getTextValue(passwordText); + if (usernameValue == null) { + errorMsg = Messages.AddRepoDialogNoUsername; + } else if (passwordValue == null) { + errorMsg = Messages.AddRepoDialogNoPassword; + } + } else { + tokenValue = IDEUtil.getTextValue(tokenText); + if (tokenValue == null) { + errorMsg = Messages.AddRepoDialogNoAccessToken; + } + } + + container.setMessage(null); + container.setErrorMessage(errorMsg); + testButton.setEnabled(errorMsg == null); + container.update(); + } + + boolean canFinish() { + if (isLogonMethod) { + return usernameValue != null && passwordValue != null; + } + return tokenValue != null; + } + + boolean isLogonMethod() { + return isLogonMethod; + } + + String getUsername() { + return usernameValue; + } + + String getPassword() { + return passwordValue; + } + + String getToken() { + return tokenValue; + } + + IAuthInfo getAuthInfo() { + return isLogonMethod ? new LogonAuth(usernameValue, passwordValue) : new TokenAuth(tokenValue); + } + + public static class LogonAuth implements IAuthInfo { + + private final String username; + private final String password; + + public LogonAuth(String username, String password) { + this.username = username; + this.password = password; + } + + @Override + public boolean isValid() { + return username != null && password != null; + } + + @Override + public String getHttpAuthorization() { + try { + String auth = username + ":" + password; + return "Basic " + Base64.getEncoder().encodeToString(auth.getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + Logger.logError("An unsupported encoding exception occurred trying to encode the logon authentication."); //$NON-NLS-1$ + } + return null; + } + } + + public static class TokenAuth implements IAuthInfo { + + private final String token; + + public TokenAuth(String token) { + this.token = token; + } + + @Override + public boolean isValid() { + return token != null; + } + + @Override + public String getHttpAuthorization() { + return "bearer " + token; + } + + } +} diff --git a/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/TemplateSourceAuthDialog.java b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/TemplateSourceAuthDialog.java new file mode 100644 index 000000000..ce4da2be2 --- /dev/null +++ b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/TemplateSourceAuthDialog.java @@ -0,0 +1,126 @@ +/******************************************************************************* + * Copyright (c) 2019 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.codewind.ui.internal.prefs; + +import java.lang.reflect.InvocationTargetException; +import java.net.URI; + +import org.eclipse.codewind.core.internal.connection.RepositoryInfo; +import org.eclipse.codewind.ui.CodewindUIPlugin; +import org.eclipse.codewind.ui.internal.wizards.CompositeContainer; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.TitleAreaDialog; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.operation.ModalContext; +import org.eclipse.jface.wizard.ProgressMonitorPart; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; + +public class TemplateSourceAuthDialog extends TitleAreaDialog implements CompositeContainer { + + private URI uri; + private RepositoryInfo repo; + private TemplateSourceAuthComposite composite; + private ProgressMonitorPart progressMon; + + public TemplateSourceAuthDialog(Shell parentShell, URI uri, RepositoryInfo repo) { + super(parentShell); + this.uri = uri; + this.repo = repo; + } + + @Override + protected void configureShell(Shell newShell) { + super.configureShell(newShell); + newShell.setText("Template Source Authorization"); + } + + @Override + protected boolean isResizable() { + return true; + } + + protected Control createDialogArea(Composite parent) { + setTitleImage(CodewindUIPlugin.getImage(CodewindUIPlugin.CODEWIND_BANNER)); + setTitle("Update Template Source Authorization"); + setMessage("Update the authorization details for the template source."); + + Composite content = (Composite) super.createDialogArea(parent); + content.setLayout(new GridLayout(1, false)); + content.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, true)); + + String username = repo.getUsername(); + composite = new TemplateSourceAuthComposite(content, this, !repo.hasAuthentication() || username != null, username); + composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + GridLayout layout = new GridLayout(); + layout.numColumns = 1; + layout.marginHeight = 10; + layout.marginWidth = 20; + progressMon = new ProgressMonitorPart(parent, layout, true); + progressMon.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + progressMon.setVisible(false); + + composite.updatePage(uri); + + return parent; + } + + @Override + protected void createButtonsForButtonBar(Composite parent) { + super.createButtonsForButtonBar(parent); + getButton(IDialogConstants.OK_ID).setEnabled(false); + } + + @Override + public void update() { + getButton(IDialogConstants.OK_ID).setEnabled(composite.canFinish()); + } + + @Override + public void run(IRunnableWithProgress runnable) throws InvocationTargetException, InterruptedException { + progressMon.setVisible(true); + try { + ModalContext.run(runnable, true, progressMon, getShell().getDisplay()); + } finally { + progressMon.done(); + progressMon.setVisible(false); + } + } + + @Override + protected Point getInitialSize() { + Point point = super.getInitialSize(); + return new Point(650, point.y); + } + + public boolean isLogonMethod() { + return composite.isLogonMethod(); + } + + public String getUsername() { + return composite.getUsername(); + } + + public String getPassword() { + return composite.getPassword(); + } + + public String getToken() { + return composite.getToken(); + } +} diff --git a/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/TemplateSourceAuthPage.java b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/TemplateSourceAuthPage.java new file mode 100644 index 000000000..853594ce9 --- /dev/null +++ b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/TemplateSourceAuthPage.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.codewind.ui.internal.prefs; + +import java.lang.reflect.InvocationTargetException; +import java.net.URI; + +import org.eclipse.codewind.core.internal.IAuthInfo; +import org.eclipse.codewind.ui.internal.messages.Messages; +import org.eclipse.codewind.ui.internal.wizards.CompositeContainer; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; + +public class TemplateSourceAuthPage extends WizardPage implements CompositeContainer { + + private TemplateSourceAuthComposite composite; + private boolean isLogonMethod = true; + private String username; + + protected TemplateSourceAuthPage(String shellTitle, String pageTitle) { + super(shellTitle); + setTitle(pageTitle); + setDescription(Messages.AddRepoAuthPageMessage); + } + + protected TemplateSourceAuthPage(String shellTitle, String pageTitle, boolean isLogonMethod, String username) { + this(shellTitle, pageTitle); + this.isLogonMethod = isLogonMethod; + this.username = username; + } + + @Override + public void createControl(Composite parent) { + Composite outer = new Composite(parent, SWT.NONE); + GridLayout layout = new GridLayout(); + layout.marginWidth = 0; + layout.marginHeight = 0; + layout.horizontalSpacing = 5; + layout.verticalSpacing = 7; + outer.setLayout(layout); + outer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + composite = new TemplateSourceAuthComposite(outer, this, isLogonMethod, username); + GridData data = new GridData(SWT.FILL, SWT.FILL, true, true); + data.widthHint = 250; + composite.setLayoutData(data); + setControl(outer); + } + + void updatePage(URI uri) { + composite.updatePage(uri); + } + + @Override + public boolean canFlipToNextPage() { + return canFinish(); + } + + boolean isActivePage() { + return isCurrentPage(); + } + + boolean canFinish() { + return composite.canFinish(); + } + + boolean isLogonMethod() { + return composite.isLogonMethod(); + } + + String getUsername() { + return composite.getUsername(); + } + + String getPassword() { + return composite.getPassword(); + } + + String getToken() { + return composite.getToken(); + } + + IAuthInfo getAuthInfo() { + return composite.getAuthInfo(); + } + + @Override + public void update() { + getWizard().getContainer().updateButtons(); + } + + @Override + public void run(IRunnableWithProgress runnable) throws InvocationTargetException, InterruptedException { + getWizard().getContainer().run(true, true, runnable); + } +} diff --git a/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/TemplateSourceDetailsPage.java b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/TemplateSourceDetailsPage.java new file mode 100644 index 000000000..4d9f9fadb --- /dev/null +++ b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/TemplateSourceDetailsPage.java @@ -0,0 +1,247 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.codewind.ui.internal.prefs; + +import java.lang.reflect.InvocationTargetException; +import java.net.URL; + +import org.eclipse.codewind.core.internal.HttpUtil; +import org.eclipse.codewind.core.internal.HttpUtil.HttpResult; +import org.eclipse.codewind.core.internal.IAuthInfo; +import org.eclipse.codewind.core.internal.Logger; +import org.eclipse.codewind.ui.CodewindUIPlugin; +import org.eclipse.codewind.ui.internal.IDEUtil; +import org.eclipse.codewind.ui.internal.messages.Messages; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.wizard.ProgressMonitorPart; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.PlatformUI; +import org.json.JSONObject; + +public class TemplateSourceDetailsPage extends WizardPage { + + public static final String DETAILS_FILE_NAME = "templates.json"; //$NON-NLS-1$ + public static final String NAME_KEY = "name"; //$NON-NLS-1$ + public static final String DESCRIPTION_KEY = "description"; //$NON-NLS-1$ + + private Text nameText, descriptionText; + private String nameValue, descriptionValue; + private String[] defaultValues = new String[2]; + private Button resetButton; + private ProgressMonitorPart progressMon; + + protected TemplateSourceDetailsPage(String shellTitle, String pageTitle) { + super(shellTitle); + setTitle(pageTitle); + setDescription(Messages.AddRepoDetailsPageMessage); + } + + protected TemplateSourceDetailsPage(String shellTitle, String pageTitle, String name, String description) { + this(shellTitle, pageTitle); + nameValue = name; + descriptionValue = description; + } + + @Override + public void createControl(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + GridLayout layout = new GridLayout(); + layout.numColumns = 2; + layout.horizontalSpacing = 5; + layout.verticalSpacing = 7; + composite.setLayout(layout); + GridData data = new GridData(GridData.FILL_HORIZONTAL); + composite.setLayoutData(data); + + Label label = new Label(composite, SWT.NONE); + label.setText(Messages.AddRepoDialogNameLabel); + label.setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER, false, false)); + + nameText = new Text(composite, SWT.BORDER); + nameText.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false)); + if (nameValue != null) { + nameText.setText(nameValue); + } + + label = new Label(composite, SWT.NONE); + label.setText(Messages.AddRepoDialogDescriptionLabel); + label.setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER, false, false)); + + descriptionText = new Text(composite, SWT.BORDER); + descriptionText.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false)); + if (descriptionValue != null) { + descriptionText.setText(descriptionValue); + } + + // Button to auto fill the name and description fields for the template source + resetButton = new Button(composite, SWT.PUSH); + resetButton.setText(Messages.AddRepoDialogResetButtonLabel); + resetButton.setToolTipText(Messages.AddRepoDialogResetButtonTooltip); + resetButton.setLayoutData(new GridData(GridData.END, GridData.FILL, false, false, 2, 1)); + + // Progress monitor widget + progressMon = new ProgressMonitorPart(parent, layout, true); + progressMon.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + progressMon.setVisible(false); + + nameText.addModifyListener((event) -> { + validate(false); + }); + descriptionText.addModifyListener((event) -> { + validate(false); + }); + + resetButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + try { + nameText.setText(defaultValues[0] == null ? "" : defaultValues[0]); + descriptionText.setText(defaultValues[1] == null ? "" : defaultValues[1]); + validate(false); + } catch (Exception e) { + Logger.logError("An error occurred trying to get the template source details", e); //$NON-NLS-1$ + } + } + }); + + resetButton.setVisible(false); + + // Add Context Sensitive Help + PlatformUI.getWorkbench().getHelpSystem().setHelp(composite, CodewindUIPlugin.MAIN_CONTEXTID); + + setControl(composite); + } + + public void updatePage(String url, IAuthInfo authInfo) { + try { + getContainer().run(true, true, getDetailsRunnable(url, authInfo)); + validate(true); + } catch (Exception e) { + Logger.logError("An error occurred trying to get the template source details", e); //$NON-NLS-1$ + } + } + + private IRunnableWithProgress getDetailsRunnable(final String url, final IAuthInfo authInfo) { + return new IRunnableWithProgress() { + @Override + public void run(IProgressMonitor monitor) throws InvocationTargetException { + try { + monitor.beginTask(Messages.AddRepoDialogAutoFillTaskLabel, IProgressMonitor.UNKNOWN); + + // Construct the URL for the details file from the template source URL + URL repoUrl = new URL(url); + String path = repoUrl.getPath(); + path = path.substring(0, path.lastIndexOf("/") + 1) + DETAILS_FILE_NAME; + URL detailsUrl = new URL(repoUrl.getProtocol(), repoUrl.getHost(), path); + + // Try to get the template source details + HttpResult result = HttpUtil.get(detailsUrl.toURI(), authInfo); + if (result.isGoodResponse && result.response != null && !result.response.isEmpty()) { + JSONObject jsonObj = new JSONObject(result.response); + String name = jsonObj.has(NAME_KEY) ? jsonObj.getString(NAME_KEY) : null; + String description = jsonObj.has(DESCRIPTION_KEY) ? jsonObj.getString(DESCRIPTION_KEY) : null; + Display.getDefault().syncExec(() -> { + // The name should at least be set + if (name == null || name.isEmpty()) { + Logger.logError("Found the template source information but the name is null or empty: " + detailsUrl); + setErrorMessage(Messages.AddRepoDialogAutoFillNotAvailableMsg); + } else { + defaultValues[0] = name; + defaultValues[1] = description; + if (nameValue == null) { + nameText.setText(name); + } + if (descriptionValue == null) { + descriptionText.setText(description == null ? "" : description); + } + } + }); + } else { + // Don't log this as an error as the template source may not provide details + Logger.log("Got error code " + result.error //$NON-NLS-1$ + + " trying to retrieve the template source details for url: " + detailsUrl //$NON-NLS-1$ + + ", and error: " + result.error); //$NON-NLS-1$ + setErrorMessage(Messages.AddRepoDialogAutoFillNotAvailableMsg); + } + } catch (Exception e) { + Logger.logError("An error occurred trying to retrieve the template source details for URL: " + url, e); //$NON-NLS-1$ + setErrorMessage(Messages.AddRepoDialogAutoFillNotAvailableMsg); + } finally { + monitor.done(); + } + } + }; + } + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + if (visible) { + nameText.setFocus(); + } + } + + private void validate(boolean init) { + String errorMsg = null; + nameValue = IDEUtil.getTextValue(nameText); + descriptionValue = IDEUtil.getTextValue(descriptionText); + resetButton.setVisible(defaultValues[0] != null); + if (resetButton.getVisible()) { + resetButton.setEnabled((nameValue == null && defaultValues[0] != null) || (nameValue != null && !nameValue.equals(defaultValues[0])) || + (descriptionValue == null && defaultValues[1] != null) || (descriptionValue != null && !descriptionValue.equals(defaultValues[1]))); + } + if (nameValue == null) { + errorMsg = Messages.AddRepoDialogNoName; + } else if (descriptionValue == null) { + errorMsg = Messages.AddRepoDialogNoDescription; + } + if (init) { + // Errors should not show when the page is first opened + setErrorMessage(null); + } else { + setErrorMessage(errorMsg); + } + getContainer().updateButtons(); + } + + @Override + public boolean canFlipToNextPage() { + return false; + } + + boolean isActivePage() { + return isCurrentPage(); + } + + boolean canFinish() { + return nameValue != null && descriptionValue != null; + } + + String getTemplateSourceName() { + return nameValue; + } + + String getTemplateSourceDescription() { + return descriptionValue; + } +} diff --git a/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/TemplateSourceURLPage.java b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/TemplateSourceURLPage.java new file mode 100644 index 000000000..46fe97eaf --- /dev/null +++ b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/prefs/TemplateSourceURLPage.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2020 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.codewind.ui.internal.prefs; + +import org.eclipse.codewind.ui.CodewindUIPlugin; +import org.eclipse.codewind.ui.internal.IDEUtil; +import org.eclipse.codewind.ui.internal.messages.Messages; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.PlatformUI; + +public class TemplateSourceURLPage extends WizardPage { + + private Text urlText; + private String urlValue; + private boolean authRequiredValue = false; + + protected TemplateSourceURLPage(String shellTitle, String pageTitle) { + super(shellTitle); + setTitle(pageTitle); + setDescription(Messages.AddRepoURLPageMessage); + } + + protected TemplateSourceURLPage(String shellTitle, String pageTitle, String url) { + this(shellTitle, pageTitle); + this.urlValue = url; + } + + @Override + public void createControl(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + GridLayout layout = new GridLayout(); + layout.numColumns = 2; + layout.horizontalSpacing = 5; + layout.verticalSpacing = 15; + composite.setLayout(layout); + GridData data = new GridData(GridData.FILL_HORIZONTAL); + composite.setLayoutData(data); + + // URL for the template source + Label label = new Label(composite, SWT.NONE); + label.setText(Messages.AddRepoDialogUrlLabel); + label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + + urlText = new Text(composite, SWT.BORDER); + urlText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + urlText.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent arg0) { + validate(); + } + }); + + Button authRequiredButton = new Button(composite, SWT.CHECK); + authRequiredButton.setText(Messages.AddRepoDialogAuthRequiredCheckboxLabel); + authRequiredButton.setToolTipText(Messages.AddRepoDialogAuthRequiredCheckboxTooltip); + authRequiredButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false, 2, 1)); + + authRequiredButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + authRequiredValue = authRequiredButton.getSelection(); + } + }); + + authRequiredButton.setSelection(authRequiredValue); + + // Add Context Sensitive Help + PlatformUI.getWorkbench().getHelpSystem().setHelp(composite, CodewindUIPlugin.MAIN_CONTEXTID); + + if (urlValue != null) { + urlText.setText(urlValue); + } + setControl(composite); + } + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + if (visible) { + urlText.setFocus(); + } + } + + private void validate() { + String errorMsg = null; + urlValue = IDEUtil.getTextValue(urlText); + if (urlValue == null) { + errorMsg = Messages.AddRepoDialogNoUrl; + } + setErrorMessage(errorMsg); + getContainer().updateButtons(); + } + + @Override + public boolean canFlipToNextPage() { + return canFinish(); + } + + boolean isActivePage() { + return isCurrentPage(); + } + + boolean canFinish() { + return urlValue != null; + } + + String getTemplateSourceUrl() { + return urlValue; + } + + boolean getAuthRequired() { + return authRequiredValue; + } +} diff --git a/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/wizards/NewCodewindProjectWizard.java b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/wizards/NewCodewindProjectWizard.java index 788f61337..4518ea3c2 100644 --- a/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/wizards/NewCodewindProjectWizard.java +++ b/dev/org.eclipse.codewind.ui/src/org/eclipse/codewind/ui/internal/wizards/NewCodewindProjectWizard.java @@ -12,7 +12,9 @@ package org.eclipse.codewind.ui.internal.wizards; import java.io.IOException; +import java.net.URI; import java.util.List; +import java.util.Optional; import java.util.concurrent.TimeoutException; import org.eclipse.codewind.core.CodewindCorePlugin; @@ -22,10 +24,13 @@ import org.eclipse.codewind.core.internal.CoreUtil; import org.eclipse.codewind.core.internal.FileUtil; import org.eclipse.codewind.core.internal.Logger; +import org.eclipse.codewind.core.internal.cli.CLIUtil.CLIException; import org.eclipse.codewind.core.internal.cli.ProjectUtil; +import org.eclipse.codewind.core.internal.cli.TemplateUtil; import org.eclipse.codewind.core.internal.connection.CodewindConnection; import org.eclipse.codewind.core.internal.connection.CodewindConnectionManager; import org.eclipse.codewind.core.internal.connection.ProjectTemplateInfo; +import org.eclipse.codewind.core.internal.connection.RepositoryInfo; import org.eclipse.codewind.ui.CodewindUIPlugin; import org.eclipse.codewind.ui.internal.UIConstants; import org.eclipse.codewind.ui.internal.actions.CodewindInstall; @@ -33,6 +38,7 @@ import org.eclipse.codewind.ui.internal.actions.OpenAppOverviewAction; import org.eclipse.codewind.ui.internal.messages.Messages; import org.eclipse.codewind.ui.internal.prefs.RegistryManagementDialog; +import org.eclipse.codewind.ui.internal.prefs.TemplateSourceAuthDialog; import org.eclipse.codewind.ui.internal.views.ViewHelper; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; @@ -153,7 +159,7 @@ public boolean performFinish() { @Override protected IStatus run(IProgressMonitor monitor) { try { - SubMonitor mon = SubMonitor.convert(monitor, 140); + SubMonitor mon = SubMonitor.convert(monitor, 220); // Check for a push registry if Codewind style project if (!connection.isLocal() && info.isCodewindStyle() && !connection.requestHasPushRegistry()) { @@ -180,20 +186,77 @@ protected Control createCustomArea(Composite parent) { return new Status(IStatus.ERROR, CodewindUIPlugin.PLUGIN_ID, Messages.NoPushRegistryError, null); } } - mon.setWorkRemaining(100); + mon.setWorkRemaining(160); - // Create and bind the project - ProjectUtil.createProject(projectName, projectPath.toOSString(), info.getUrl(), connection.getConid(), mon.split(40)); + // Create the project - allow user to update template source credentials if necessary + try { + ProjectUtil.createProject(projectName, projectPath.toOSString(), info.getUrl(), connection.getConid(), mon.split(40)); + } catch (CLIException e) { + if (mon.isCanceled()) { + return Status.CANCEL_STATUS; + } + + // Check for the error id that indicates an authorization failure and allow the + // user to update their credentials + if ("change_me_to_the_auth_error_id".equals(e.errorId)) { + // Find the template source associated with the selected template + String sourceName = info.getSource(); + Optional repoResult = TemplateUtil + .listTemplateSources(connection.getConid(), mon.split(10)).stream() + .filter(repo -> repo.getName().equals(sourceName)).findFirst(); + if (!repoResult.isPresent()) { + Logger.logError("Could not find a template source with the name: " + sourceName); + throw e; + } + + // Display the dialog + URI uri = new URI(repoResult.get().getURL()); + Display.getDefault().syncExec(() -> { + TemplateSourceAuthDialog dialog = new TemplateSourceAuthDialog(Display.getDefault().getActiveShell(), uri, repoResult.get()); + if (dialog.open() == MessageDialog.OK) { + try { + TemplateUtil.removeTemplateSource(repoResult.get().getURL(), connection.getConid(), mon.split(10)); + TemplateUtil.addTemplateSource(repoResult.get().getURL(), dialog.getUsername(), + dialog.getPassword(), dialog.getToken(), repoResult.get().getName(), + repoResult.get().getDescription(), connection.getConid(), mon.split(10)); + } catch (Exception e1) { + Logger.logError("An error occurred while trying to update the authentication details for the tempate source: " + repoResult.get().getURL(), e1); + MessageDialog.openError(Display.getDefault().getActiveShell(), + "Template Source Update Error", + "An error occurred while trying to update the authentication details for the " + + repoResult.get().getName() + " template source: " + + e1.toString()); + mon.setCanceled(true); + } + } else { + mon.setCanceled(true); + } + }); + + if (mon.isCanceled()) { + return Status.CANCEL_STATUS; + } + + // Try to create the project again + ProjectUtil.createProject(projectName, projectPath.toOSString(), info.getUrl(), connection.getConid(), mon.split(40)); + } else { + throw e; + } + } if (mon.isCanceled()) { cleanup(projectName, projectPath, connection); return Status.CANCEL_STATUS; } + mon.setWorkRemaining(70); + + // Bind the project ProjectUtil.bindProject(projectName, projectPath.toOSString(), info.getLanguage(), info.getProjectType(), connection.getConid(), mon.split(40)); if (mon.isCanceled()) { cleanup(projectName, projectPath, connection); return Status.CANCEL_STATUS; } mon.split(10); + connection.refreshApps(null); if (mon.isCanceled()) { return Status.CANCEL_STATUS;