This is an automated email from the ASF dual-hosted git repository.

malliaridis pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new f9f45472662 SOLR-17885: Fix authentication for new UI in browser 
(#3704)
f9f45472662 is described below

commit f9f45472662c44644b797adab7a5131128c61fd2
Author: Christos Malliaridis <[email protected]>
AuthorDate: Wed Oct 1 01:06:33 2025 +0300

    SOLR-17885: Fix authentication for new UI in browser (#3704)
    
    * Add assets of new UI to excludePatterns
    
    * Resolve issues with Basic auth on web and implement realm support
---
 .../commonMain/composeResources/values/strings.xml |  1 +
 .../solr/ui/components/auth/BasicAuthComponent.kt  |  2 +-
 .../integration/DefaultAuthenticationComponent.kt  | 13 ++++---
 .../start/integration/HttpStartStoreClient.kt      | 41 +++++++++++++++++-----
 .../kotlin/org/apache/solr/ui/domain/AuthMethod.kt |  4 ++-
 .../kotlin/org/apache/solr/ui/domain/AuthOption.kt |  4 +--
 .../org/apache/solr/ui/utils/HttpClientUtils.kt    |  8 ++++-
 .../apache/solr/ui/views/auth/BasicAuthContent.kt  |  5 ++-
 solr/webapp/web/WEB-INF/web.xml                    |  2 +-
 9 files changed, 57 insertions(+), 23 deletions(-)

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 @@
   <!-- Descriptions (desc) -->
   <string name="desc_to_get_started">To get started, please provide a Solr 
host URL:</string>
   <string name="desc_solr_instance_with_auth">The Solr instance %1$s has 
enabled authentication.</string>
+  <string name="desc_sign_in_with_credentials">To authenticate you may use 
your credentials for Basic authentication.</string>
   <string name="desc_sign_in_with_credentials_to_realm">To authenticate you 
may use your credentials for the realm %1$s.</string>
 
   <!-- Errors (error) -->
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<AuthMethod> {
-        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<String, 
Map<String, String>> {
+        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 @@
     -->
     <init-param>
       <param-name>excludePatterns</param-name>
-      
<param-value>/partials/.+,/libs/.+,/css/.+,/js/.+,/img/.+,/templates/.+</param-value>
+      
<param-value>/partials/.+,/libs/.+,/css/.+,/js/.+,/img/.+,/templates/.+,/ui/.*</param-value>
     </init-param>
   </filter>
 

Reply via email to