Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ class TemplateRequestExecutor(
}
val safeRequestHeaders = redactHeaders(effectiveRequestHeaders)
val safeRequestBody = redactSensitivePayload(renderedBody)
val curlCommand = toCurlCommand(config.method.uppercase(), renderedUrl, effectiveRequestHeaders, renderedBody)
LlmRuntimeLogger.info(
"Template request start | method=${config.method.uppercase()} | url=$renderedUrl | headers=$safeRequestHeaders | body=$safeRequestBody"
"Template request start | method=${config.method.uppercase()} | url=$renderedUrl | headers=$safeRequestHeaders | body=$safeRequestBody | curl=$curlCommand"
)

val response = http.request {
Expand Down Expand Up @@ -136,6 +137,29 @@ class TemplateRequestExecutor(
return "***"
}

private fun toCurlCommand(method: String, url: String, headers: Map<String, String>, body: String): String {
val headerArgs = headers.entries.joinToString(" ") { (name, value) ->
"-H " + shellQuote("$name: ${redactHeaderValue(name, value)}")
}
val redactedBody = redactSensitivePayload(body)
val bodyArg = if (redactedBody.isNotBlank()) " --data " + shellQuote(redactedBody) else ""
return "curl -X $method " + shellQuote(url) +
(if (headerArgs.isNotBlank()) " $headerArgs" else "") +
bodyArg
}

private fun redactHeaderValue(name: String, value: String): String {
return if (name.contains("authorization", true) ||
name.contains("token", true) ||
name.contains("key", true) ||
name.contains("secret", true) ||
name.contains("password", true) ||
name.contains("cookie", true)
) "***" else value
}

private fun shellQuote(value: String): String = "'" + value.replace("'", "'\"'\"'") + "'"

private companion object {
const val MAX_LOG_BODY_CHARS = 4_000
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.openprojectx.ai.plugin

import com.intellij.openapi.options.SearchableConfigurable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
Expand Down Expand Up @@ -336,10 +337,10 @@ class AiTestSettingsConfigurable(
ApplicationManager.getApplication().executeOnPooledThread {
runCatching(block)
.onSuccess { value ->
ApplicationManager.getApplication().invokeLater { onSuccess(value) }
ApplicationManager.getApplication().invokeLater({ onSuccess(value) }, ModalityState.any())
}
.onFailure { ex ->
ApplicationManager.getApplication().invokeLater { onFailure(ex) }
ApplicationManager.getApplication().invokeLater({ onFailure(ex) }, ModalityState.any())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.intellij.credentialStore.CredentialAttributes
import com.intellij.credentialStore.Credentials
import com.intellij.ide.passwordSafe.PasswordSafe
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
Expand Down Expand Up @@ -77,13 +78,13 @@ class LlmAuthSessionService(
ApplicationManager.getApplication().executeOnPooledThread {
try {
loginNow()
ApplicationManager.getApplication().invokeLater {
ApplicationManager.getApplication().invokeLater({
Messages.showInfoMessage(project, "LLM login succeeded.", "Code Quality Improver")
}
}, ModalityState.any())
} catch (e: Exception) {
ApplicationManager.getApplication().invokeLater {
ApplicationManager.getApplication().invokeLater({
Messages.showErrorDialog(project, detailedErrorMessage("LLM login failed", e), "Code Quality Improver")
}
}, ModalityState.any())
}
}
}
Expand Down Expand Up @@ -159,7 +160,7 @@ class LlmAuthSessionService(
if (app.isDispatchThread) {
showDialog()
} else {
app.invokeAndWait(showDialog)
app.invokeAndWait(showDialog, ModalityState.any())
}
return credentials
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1445,8 +1445,9 @@ object LlmSettingsLoader {
} else {
"Authorization=<absent>"
}
val curlCommand = buildBitbucketCurlCommand(url, normalized, credentials)
RuntimeLogStore.append(
"INFO | Bitbucket API | Request method=GET url=$url headers[$authHeaderLog] credentialSources=${credentials.joinToString(",") { it.source }.ifBlank { "<none>" }}"
"INFO | Bitbucket API | Request method=GET url=$url headers[$authHeaderLog] credentialSources=${credentials.joinToString(",") { it.source }.ifBlank { "<none>" }} | curl=$curlCommand"
)
val code = conn.responseCode
val body = (if (code in 200..299) conn.inputStream else conn.errorStream)
Expand All @@ -1465,6 +1466,25 @@ object LlmSettingsLoader {
return body
}


private fun buildBitbucketCurlCommand(url: String, token: String, credentials: List<BitbucketCredential>): String {
val authorizationHeader = when {
token.isNotBlank() && token.contains(":") -> {
val username = token.substringBefore(':')
"Authorization: Basic ${displayHeaderValue(username)}:***"
}
token.isNotBlank() -> "Authorization: Bearer ***"
credentials.isNotEmpty() -> {
val credential = credentials.first()
"Authorization: Basic ${displayHeaderValue(credential.username)}:***"
}
else -> null
}
val authPart = authorizationHeader?.let { " -H " + shellQuote(it) }.orEmpty()
return "curl -X GET " + shellQuote(url) + authPart
}

private fun shellQuote(value: String): String = "'" + value.replace("'", "'\"'\"'") + "'"
private fun describeBasicTokenHeader(token: String): String {
val separatorIndex = token.indexOf(':')
val username = token.substring(0, separatorIndex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ private data class SonarQubeCoverageRequest(
val password: String,
val targetCoverage: Double,
val maxFiles: Int,
val generateMissingTests: Boolean
val generateMissingTests: Boolean,
val skipPkixCheck: Boolean
)

private class SonarQubeCoverageDialog(
Expand All @@ -108,6 +109,7 @@ private class SonarQubeCoverageDialog(
private val targetCoverageField = JTextField(config.targetCoverage.toString(), 8)
private val maxFilesField = JTextField(config.maxFiles.toString(), 8)
private val generateMissingTestsBox = JCheckBox("Generate missing tests with AI", true)
private val skipPkixCheckBox = JCheckBox("Skip PKIX/TLS certificate check", true)

init {
title = "SonarQube Coverage"
Expand All @@ -131,6 +133,7 @@ private class SonarQubeCoverageDialog(
add(JLabel("Max uncovered files to inspect"))
add(maxFilesField)
add(generateMissingTestsBox)
add(skipPkixCheckBox)
}

fun request(): SonarQubeCoverageRequest = SonarQubeCoverageRequest(
Expand All @@ -141,7 +144,8 @@ private class SonarQubeCoverageDialog(
password = String(passwordField.password).trim(),
targetCoverage = targetCoverageField.text.trim().toDoubleOrNull() ?: 80.0,
maxFiles = maxFilesField.text.trim().toIntOrNull()?.coerceIn(1, 20) ?: 5,
generateMissingTests = generateMissingTestsBox.isSelected
generateMissingTests = generateMissingTestsBox.isSelected,
skipPkixCheck = skipPkixCheckBox.isSelected
)
}

Expand Down Expand Up @@ -170,7 +174,7 @@ private class SonarQubeCoverageClient(private val request: SonarQubeCoverageRequ
)

suspend fun loadCoverage(): SonarQubeCoverageReport {
val jsonClient = HttpClients.shared(timeoutSeconds = 60)
val jsonClient = HttpClients.shared(disableTlsVerification = request.skipPkixCheck, timeoutSeconds = 60)
try {
val baseUrl = request.serverUrl.trimEnd('/')
val component = encoded(request.projectKey)
Expand Down
Loading