diff options
Diffstat (limited to 'plugins/tasks/tasks-core')
6 files changed, 121 insertions, 68 deletions
diff --git a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/JiraRepository.java b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/JiraRepository.java index b8004ca70023..fa80fb161188 100644 --- a/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/JiraRepository.java +++ b/plugins/tasks/tasks-core/jira/src/com/intellij/tasks/jira/JiraRepository.java @@ -12,7 +12,6 @@ import com.intellij.tasks.Task; import com.intellij.tasks.TaskBundle; import com.intellij.tasks.TaskState; import com.intellij.tasks.impl.BaseRepositoryImpl; -import com.intellij.tasks.impl.TaskUtil; import com.intellij.tasks.impl.gson.GsonUtil; import com.intellij.tasks.jira.rest.JiraRestApi; import com.intellij.tasks.jira.soap.JiraLegacyApi; @@ -52,6 +51,7 @@ public class JiraRepository extends BaseRepositoryImpl { private static final boolean REDISCOVER_API = Boolean.getBoolean("tasks.jira.rediscover.api"); public static final Pattern JIRA_ID_PATTERN = Pattern.compile("\\p{javaUpperCase}+-\\d+"); + public static final String AUTH_COOKIE_NAME = "JSESSIONID"; /** * Default JQL query @@ -145,12 +145,13 @@ public class JiraRepository extends BaseRepositoryImpl { @Nullable @Override public CancellableConnection createCancellableConnection() { + clearCookies(); // TODO cancellable connection for XML_RPC? return new CancellableConnection() { @Override protected void doTest() throws Exception { ensureApiVersionDiscovered(); - myApiVersion.findTasks("", 1); + myApiVersion.findTasks(mySearchQuery, 1); } @Override @@ -226,20 +227,18 @@ public class JiraRepository extends BaseRepositoryImpl { HttpClient client = getHttpClient(); // Fix for https://jetbrains.zendesk.com/agent/#/tickets/24566 // See https://confluence.atlassian.com/display/ONDEMANDKB/Getting+randomly+logged+out+of+OnDemand for details - if (BASIC_AUTH_ONLY) { + // IDEA-128824, IDEA-128706 Use cookie authentication only for JIRA on-Demand + // TODO Make JiraVersion more suitable for such checks + final boolean isJiraOnDemand = StringUtil.notNullize(myJiraVersion).contains("OD"); + if (isJiraOnDemand) { + LOG.info("Connecting to JIRA on-Demand. Cookie authentication is enabled unless 'tasks.jira.basic.auth.only' VM flag is used."); + } + if (BASIC_AUTH_ONLY || !isJiraOnDemand) { // to override persisted settings setUseHttpAuthentication(true); } else { - boolean cookieAuthenticated = false; - for (Cookie cookie : client.getState().getCookies()) { - if (cookie.getName().equals("JSESSIONID") && !cookie.isExpired()) { - cookieAuthenticated = true; - break; - } - } - // disable subsequent basic authorization attempts if user already was authenticated - boolean enableBasicAuthentication = !(isRestApiSupported() && cookieAuthenticated); + boolean enableBasicAuthentication = !(isRestApiSupported() && containsCookie(client, AUTH_COOKIE_NAME)); if (enableBasicAuthentication != isUseHttpAuthentication()) { LOG.info("Basic authentication for subsequent requests was " + (enableBasicAuthentication ? "enabled" : "disabled")); } @@ -251,14 +250,15 @@ public class JiraRepository extends BaseRepositoryImpl { // may be null if 204 No Content received final InputStream stream = method.getResponseBodyAsStream(); String entityContent = stream == null ? "" : StreamUtil.readText(stream, CharsetToolkit.UTF8); - TaskUtil.prettyFormatJsonToLog(LOG, entityContent); + //TaskUtil.prettyFormatJsonToLog(LOG, entityContent); // besides SC_OK, can also be SC_NO_CONTENT in issue transition requests // see: JiraRestApi#setTaskStatus //if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) { if (statusCode >= 200 && statusCode < 300) { return entityContent; } - else if (method.getResponseHeader("Content-Type") != null) { + clearCookies(); + if (method.getResponseHeader("Content-Type") != null) { Header header = method.getResponseHeader("Content-Type"); if (header.getValue().startsWith("application/json")) { JsonObject object = GSON.fromJson(entityContent, JsonObject.class); @@ -284,6 +284,19 @@ public class JiraRepository extends BaseRepositoryImpl { throw new Exception(TaskBundle.message("failure.http.error", statusCode, statusText)); } + private static boolean containsCookie(@NotNull HttpClient client, @NotNull String cookieName) { + for (Cookie cookie : client.getState().getCookies()) { + if (cookie.getName().equals(cookieName) && !cookie.isExpired()) { + return true; + } + } + return false; + } + + private void clearCookies() { + getHttpClient().getState().clearCookies(); + } + // Made public for SOAP API compatibility @Override public HttpClient getHttpClient() { diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/SwitchTaskCombo.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/SwitchTaskCombo.java index f053f5012969..622c47dc552c 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/SwitchTaskCombo.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/SwitchTaskCombo.java @@ -81,7 +81,7 @@ public class SwitchTaskCombo extends ComboBoxAction implements DumbAware { public void update(AnActionEvent e) { Project project = e.getData(CommonDataKeys.PROJECT); Presentation presentation = e.getPresentation(); - if (project == null || project.isDisposed() || (ActionPlaces.MAIN_MENU.equals(e.getPlace()) && findFrame(e) == null)) { + if (project == null || project.isDisposed() || (ActionPlaces.isMainMenuOrActionSearch(e.getPlace()) && findFrame(e) == null)) { presentation.setEnabled(false); presentation.setText(""); presentation.setIcon(null); diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/gitlab/GitlabRepository.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/gitlab/GitlabRepository.java index cc66448c94e9..9df0eb07b26e 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/gitlab/GitlabRepository.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/gitlab/GitlabRepository.java @@ -4,7 +4,6 @@ import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.intellij.openapi.util.Comparing; import com.intellij.tasks.Task; -import com.intellij.tasks.TaskBundle; import com.intellij.tasks.TaskRepositoryType; import com.intellij.tasks.gitlab.model.GitlabIssue; import com.intellij.tasks.gitlab.model.GitlabProject; @@ -14,7 +13,9 @@ import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.xmlb.annotations.Tag; import com.intellij.util.xmlb.annotations.Transient; -import org.apache.http.*; +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpGet; import org.apache.http.protocol.HttpContext; @@ -118,24 +119,7 @@ public class GitlabRepository extends NewBaseRepositoryImpl { @Nullable @Override public CancellableConnection createCancellableConnection() { - return new CancellableConnection() { - private HttpGet myRequest = new HttpGet(getIssuesUrl()); - - @Override - protected void doTest() throws Exception { - HttpResponse response = getHttpClient().execute(myRequest); - StatusLine statusLine = response.getStatusLine(); - if (statusLine != null && statusLine.getStatusCode() != HttpStatus.SC_OK) { - throw new Exception(TaskBundle.message("failure.http.error", statusLine.getStatusCode(), statusLine.getReasonPhrase())); - } - } - - // TODO: find more about proper request aborting in HttpClient4.x - @Override - public void cancel() { - myRequest.abort(); - } - }; + return new HttpTestConnection(new HttpGet(getIssuesUrl())); } /** diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/httpclient/NewBaseRepositoryImpl.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/httpclient/NewBaseRepositoryImpl.java index c1648fc8f490..082d49812d5b 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/httpclient/NewBaseRepositoryImpl.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/httpclient/NewBaseRepositoryImpl.java @@ -3,6 +3,7 @@ package com.intellij.tasks.impl.httpclient; import com.intellij.tasks.TaskRepositoryType; import com.intellij.tasks.config.TaskSettings; import com.intellij.tasks.impl.BaseRepository; +import com.intellij.tasks.impl.RequestFailedException; import com.intellij.tasks.impl.TaskUtil; import com.intellij.util.net.HttpConfigurable; import com.intellij.util.net.ssl.CertificateManager; @@ -14,6 +15,7 @@ import org.apache.http.client.CredentialsProvider; import org.apache.http.client.HttpClient; import org.apache.http.client.config.AuthSchemes; import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.conn.ssl.X509HostnameVerifier; import org.apache.http.impl.auth.BasicScheme; @@ -154,4 +156,44 @@ public abstract class NewBaseRepositoryImpl extends BaseRepository { } } } + + public class HttpTestConnection extends CancellableConnection { + + // Request can be changed during test + protected volatile HttpRequestBase myCurrentRequest; + + public HttpTestConnection(@NotNull HttpRequestBase request) { + myCurrentRequest = request; + } + + @Override + protected void doTest() throws Exception { + try { + test(); + } + catch (IOException e) { + // Depending on request state AbstractExecutionAwareRequest.abort() can cause either + // * RequestAbortedException if connection was not yet leased + // * InterruptedIOException before reading response + // * SocketException("Socket closed") during reading response + // However in all cases 'aborted' flag should be properly set + if (!myCurrentRequest.isAborted()) { + throw e; + } + } + } + + protected void test() throws Exception { + HttpResponse response = getHttpClient().execute(myCurrentRequest); + StatusLine statusLine = response.getStatusLine(); + if (statusLine != null && statusLine.getStatusCode() != HttpStatus.SC_OK) { + throw RequestFailedException.forStatusCode(statusLine.getStatusCode(), statusLine.getReasonPhrase()); + } + } + + @Override + public void cancel() { + myCurrentRequest.abort(); + } + } } diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/redmine/RedmineRepository.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/redmine/RedmineRepository.java index e63b2b365e60..a4b10386aeba 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/redmine/RedmineRepository.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/redmine/RedmineRepository.java @@ -16,6 +16,7 @@ import com.intellij.util.xmlb.annotations.Tag; import com.intellij.util.xmlb.annotations.Transient; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; +import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.utils.URIBuilder; @@ -23,6 +24,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -107,28 +110,36 @@ public class RedmineRepository extends NewBaseRepositoryImpl { return new RedmineRepository(this); } + @Nullable @Override - public void testConnection() throws Exception { - // Strangely, Redmine doesn't return 401 or 403 error codes, if client sent wrong credentials, and instead - // merely returns empty array of issues with status code of 200. This means that we should attempt to fetch - // something more specific than issues to test proper configuration, e.g. current user information at - // /users/current.json. Unfortunately this endpoint may be unavailable on some old servers (see IDEA-122845) - // and in this case we have to come back to requesting issues in this case to test anything at all. - HttpClient client = getHttpClient(); - URIBuilder uriBuilder = new URIBuilder(getRestApiUrl("users", "current.json")); - if (isUseApiKeyAuthentication()) { - uriBuilder.addParameter("key", getAPIKey()); - } - HttpResponse response = client.execute(new HttpGet(uriBuilder.build())); - //TaskUtil.prettyFormatResponseToLog(LOG, response); - int code = response.getStatusLine().getStatusCode(); - if (code == HttpStatus.SC_NOT_FOUND) { - getIssues("", 0, 1, true); - return; - } - if (code != HttpStatus.SC_OK) { - throw RequestFailedException.forStatusCode(code); - } + public CancellableConnection createCancellableConnection() { + return new NewBaseRepositoryImpl.HttpTestConnection(new HttpGet()) { + @Override + protected void test() throws Exception { + // Strangely, Redmine doesn't return 401 or 403 error codes, if client sent wrong credentials, and instead + // merely returns empty array of issues with status code of 200. This means that we should attempt to fetch + // something more specific than issues to test proper configuration, e.g. current user information at + // /users/current.json. Unfortunately this endpoint may be unavailable on some old servers (see IDEA-122845) + // and in this case we have to come back to requesting issues in this case to test anything at all. + + URIBuilder uriBuilder = new URIBuilder(getRestApiUrl("users", "current.json")); + if (isUseApiKeyAuthentication()) { + uriBuilder.addParameter("key", getAPIKey()); + } + myCurrentRequest.setURI(uriBuilder.build()); + HttpClient client = getHttpClient(); + + HttpResponse httpResponse = client.execute(myCurrentRequest); + StatusLine statusLine = httpResponse.getStatusLine(); + if (statusLine != null && statusLine.getStatusCode() == HttpStatus.SC_NOT_FOUND) { + myCurrentRequest = new HttpGet(getIssuesUrl(0, 1, true)); + statusLine = client.execute(myCurrentRequest).getStatusLine(); + } + if (statusLine != null && statusLine.getStatusCode() != HttpStatus.SC_OK) { + throw RequestFailedException.forStatusCode(statusLine.getStatusCode(), statusLine.getReasonPhrase()); + } + } + }; } @Override @@ -144,12 +155,6 @@ public class RedmineRepository extends NewBaseRepositoryImpl { public List<RedmineIssue> fetchIssues(String query, int offset, int limit, boolean withClosed) throws Exception { ensureProjectsDiscovered(); - URIBuilder builder = new URIBuilder(getRestApiUrl("issues.json")) - .addParameter("offset", String.valueOf(offset)) - .addParameter("limit", String.valueOf(limit)) - .addParameter("status_id", withClosed ? "*" : "open") - .addParameter("assigned_to_id", "me"); - // Legacy API, can't find proper documentation //if (StringUtil.isNotEmpty(query)) { // builder.addParameter("fields[]", "subject").addParameter("operators[subject]", "~").addParameter("values[subject][]", query); @@ -158,15 +163,24 @@ public class RedmineRepository extends NewBaseRepositoryImpl { //if (myCurrentProject != null && myCurrentProject != UNSPECIFIED_PROJECT) { // builder.addParameter("project_id", String.valueOf(myCurrentProject.getId())); //} - if (isUseApiKeyAuthentication()) { - builder.addParameter("key", myAPIKey); - } HttpClient client = getHttpClient(); - HttpGet method = new HttpGet(builder.toString()); + HttpGet method = new HttpGet(getIssuesUrl(offset, limit, withClosed)); IssuesWrapper wrapper = client.execute(method, new GsonSingleObjectDeserializer<IssuesWrapper>(GSON, IssuesWrapper.class)); return wrapper == null ? Collections.<RedmineIssue>emptyList() : wrapper.getIssues(); } + private URI getIssuesUrl(int offset, int limit, boolean withClosed) throws URISyntaxException { + URIBuilder builder = new URIBuilder(getRestApiUrl("issues.json")) + .addParameter("offset", String.valueOf(offset)) + .addParameter("limit", String.valueOf(limit)) + .addParameter("status_id", withClosed ? "*" : "open") + .addParameter("assigned_to_id", "me"); + if (isUseApiKeyAuthentication()) { + builder.addParameter("key", myAPIKey); + } + return builder.build(); + } + public List<RedmineProject> fetchProjects() throws Exception { HttpClient client = getHttpClient(); // Download projects with pagination (IDEA-125056, IDEA-125157) diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/redmine/model/RedmineIssue.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/redmine/model/RedmineIssue.java index 07b5669a1540..aea41bdb6ca8 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/redmine/model/RedmineIssue.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/redmine/model/RedmineIssue.java @@ -19,7 +19,7 @@ public class RedmineIssue { private IssueStatus status; @Mandatory private String subject; - @Mandatory + // IDEA-126470 May be missing if issue was not created via web-interface private String description; @SerializedName("done_ratio") private int doneRatio; @@ -45,7 +45,7 @@ public class RedmineIssue { return subject; } - @NotNull + @Nullable public String getDescription() { return description; } |