diff --git a/solr/ui/src/commonMain/composeResources/values/strings.xml b/solr/ui/src/commonMain/composeResources/values/strings.xml
index 6b4a6841c74..42becfb8734 100644
--- a/solr/ui/src/commonMain/composeResources/values/strings.xml
+++ b/solr/ui/src/commonMain/composeResources/values/strings.xml
@@ -30,6 +30,7 @@
To get started, please provide a Solr host URL:
The Solr instance %1$s has enabled authentication.
+ To authenticate you may use your credentials for Basic authentication.
To authenticate you may use your credentials for the realm %1$s.
diff --git a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/auth/BasicAuthComponent.kt b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/auth/BasicAuthComponent.kt
index c18c2e48920..c243ad8ccbb 100644
--- a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/auth/BasicAuthComponent.kt
+++ b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/auth/BasicAuthComponent.kt
@@ -52,7 +52,7 @@ interface BasicAuthComponent {
* does not distinguish input fields.
*/
data class Model(
- val realm: String = "",
+ val realm: String? = null,
val username: String = "",
val password: String = "",
val hasError: Boolean = false,
diff --git a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/auth/integration/DefaultAuthenticationComponent.kt b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/auth/integration/DefaultAuthenticationComponent.kt
index 4fb7493bd00..0aae2090309 100644
--- a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/auth/integration/DefaultAuthenticationComponent.kt
+++ b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/auth/integration/DefaultAuthenticationComponent.kt
@@ -100,12 +100,11 @@ class DefaultAuthenticationComponent(
}
init {
- lifecycle.doOnCreate {
- methods.forEach { method ->
- when (method) {
- is AuthMethod.BasicAuthMethod ->
- basicAuthNavigation.activate(configuration = BasicAuthConfiguration(method))
- }
+ methods.forEach { method ->
+ when (method) {
+ is AuthMethod.BasicAuthMethod ->
+ basicAuthNavigation.activate(configuration = BasicAuthConfiguration(method))
+ is AuthMethod.Unknown -> {} // TODO Handle unknown auth methods
}
}
}
@@ -120,9 +119,9 @@ class DefaultAuthenticationComponent(
OnAuthenticated(
option = AuthOption.BasicAuthOption(
url = url,
- method = output.method,
username = output.username,
password = output.password,
+ realm = output.method.realm,
),
),
)
diff --git a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/start/integration/HttpStartStoreClient.kt b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/start/integration/HttpStartStoreClient.kt
index dfed6e3c921..885fccefe2e 100644
--- a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/start/integration/HttpStartStoreClient.kt
+++ b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/start/integration/HttpStartStoreClient.kt
@@ -64,7 +64,7 @@ class HttpStartStoreClient(
methods = methods,
message =
if (methods.isEmpty()) {
- "Unauthorized response received with missing or unsupported auth method."
+ "Unauthorized response received with missing auth methods."
} else {
null
},
@@ -83,15 +83,38 @@ class HttpStartStoreClient(
* @param headers The headers to use for extracting the information.
*/
private fun getAuthMethodsFromHeader(headers: Headers): List {
- val authHeader = headers["Www-Authenticate"]
- val parts = authHeader?.split(" ", limit = 2)
- val scheme = parts?.firstOrNull()
+ // Note that on JVM headers.getAll() may return multiple values, whereas in WebAssembly/JS
+ // it may merge multiple headers and separate them by comma
+ val authHeaders = headers.getAll("Www-Authenticate") ?: emptyList()
- // TODO Get realm from header value
+ return authHeaders
+ // Split by comma, as there is the chance that headers will be merged and separated by
+ // comma (e.g. on web target)
+ .flatMap { header -> header.split(",") }
+ .map { authHeader ->
+ val (scheme, params) = parseWwwAuthenticate(authHeader)
- return when (scheme) {
- "Basic" -> listOf(AuthMethod.BasicAuthMethod(realm = "solr"))
- else -> emptyList()
- }
+ when (scheme.lowercase()) {
+ "basic", "xbasic" -> AuthMethod.BasicAuthMethod(realm = params["realm"])
+ else -> AuthMethod.Unknown
+ }
+ }
+ }
+
+ private fun parseWwwAuthenticate(headerValue: String): Pair> {
+ val parts = headerValue.split(" ", limit = 2)
+ val scheme = parts[0]
+
+ // The below mapping is not supporting commas or spaces in the parameter values, nor
+ // parameters separated by commas (only spaces)
+ val params = parts.getOrNull(1)
+ ?.split(" ")
+ ?.map(String::trim)
+ ?.associate {
+ val (k, v) = it.split("=", limit = 2)
+ k to v.trim('"')
+ }
+ ?: emptyMap()
+ return scheme to params
}
}
diff --git a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/domain/AuthMethod.kt b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/domain/AuthMethod.kt
index c9b711b2d43..e50d0429290 100644
--- a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/domain/AuthMethod.kt
+++ b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/domain/AuthMethod.kt
@@ -35,5 +35,7 @@ sealed interface AuthMethod {
* @property realm The realm of the basic auth.
*/
@Serializable
- data class BasicAuthMethod(val realm: String = "") : AuthMethod
+ data class BasicAuthMethod(val realm: String? = null) : AuthMethod
+
+ data object Unknown : AuthMethod
}
diff --git a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/domain/AuthOption.kt b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/domain/AuthOption.kt
index 637db9579d9..9665d7aa19c 100644
--- a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/domain/AuthOption.kt
+++ b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/domain/AuthOption.kt
@@ -38,17 +38,17 @@ sealed interface AuthOption {
/**
* Authentication option for authenticating with basic auth (credentials).
*
- * @property method The basic auth method used for authentication (holds metadata).
* @property url The URL of the instance that requires basic auth and the credentials of this
* instance are for.
* @property username The username to use for further authenticated requests.
* @property password The password to use for further authenticated requests.
+ * @property realm The realm defined and used in the Basic auth method.
*/
@Serializable
data class BasicAuthOption(
- val method: AuthMethod.BasicAuthMethod,
val url: Url,
val username: String,
val password: String,
+ val realm: String? = null,
) : AuthOption
}
diff --git a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/utils/HttpClientUtils.kt b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/utils/HttpClientUtils.kt
index e8b409a9138..98e6d146ed3 100644
--- a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/utils/HttpClientUtils.kt
+++ b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/utils/HttpClientUtils.kt
@@ -57,19 +57,25 @@ fun getHttpClientWithAuthOption(option: AuthOption) = when (option) {
is AuthOption.None -> getDefaultClient(option.url)
is AuthOption.BasicAuthOption -> getHttpClientWithCredentials(
url = option.url,
+ realm = option.realm,
username = option.username,
password = option.password,
)
}
fun getHttpClientWithCredentials(
- url: Url = Url("http://127.0.0.1:8983/"),
username: String,
password: String,
+ url: Url = Url("http://127.0.0.1:8983/"),
+ realm: String? = null,
) = getDefaultClient(url) {
install(Auth) {
basic {
credentials { BasicAuthCredentials(username, password) }
+ // Always include the credentials, because we are accessing protected endpoints from a
+ // not protected asset (web-assembly app)
+ sendWithoutRequest { true }
+ this.realm = realm
}
}
}
diff --git a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/auth/BasicAuthContent.kt b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/auth/BasicAuthContent.kt
index c3a1b1a53c5..3cb87fbba3d 100644
--- a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/auth/BasicAuthContent.kt
+++ b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/auth/BasicAuthContent.kt
@@ -35,6 +35,7 @@ import org.apache.solr.ui.components.auth.BasicAuthComponent
import org.apache.solr.ui.generated.resources.Res
import org.apache.solr.ui.generated.resources.action_sign_in_with_credentials
import org.apache.solr.ui.generated.resources.authenticating
+import org.apache.solr.ui.generated.resources.desc_sign_in_with_credentials
import org.apache.solr.ui.generated.resources.desc_sign_in_with_credentials_to_realm
import org.apache.solr.ui.generated.resources.label_password
import org.apache.solr.ui.generated.resources.label_username
@@ -62,7 +63,9 @@ fun BasicAuthContent(
val model by component.model.collectAsState()
Text(
- text = stringResource(Res.string.desc_sign_in_with_credentials_to_realm, model.realm),
+ text = model.realm?.let {
+ stringResource(Res.string.desc_sign_in_with_credentials_to_realm, it)
+ } ?: stringResource(Res.string.desc_sign_in_with_credentials),
style = MaterialTheme.typography.bodyMedium,
)
diff --git a/solr/webapp/web/WEB-INF/web.xml b/solr/webapp/web/WEB-INF/web.xml
index dfc0c0ce454..37a68b4692b 100644
--- a/solr/webapp/web/WEB-INF/web.xml
+++ b/solr/webapp/web/WEB-INF/web.xml
@@ -35,7 +35,7 @@
-->
excludePatterns
- /partials/.+,/libs/.+,/css/.+,/js/.+,/img/.+,/templates/.+
+ /partials/.+,/libs/.+,/css/.+,/js/.+,/img/.+,/templates/.+,/ui/.*