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

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


The following commit(s) were added to refs/heads/main by this push:
     new 19aaccb  feat(config): support HTTP Basic Authentication credentials 
(#89)
19aaccb is described below

commit 19aaccb15b3daf2a2b738cfff0b81c967558872f
Author: Yunus Emre KORKMAZ <[email protected]>
AuthorDate: Mon Jun 15 19:25:38 2026 +0300

    feat(config): support HTTP Basic Authentication credentials (#89)
    
    Add optional `solr.username` / `solr.password` configuration properties
    (also bound from the `SOLR_USERNAME` / `SOLR_PASSWORD` environment
    variables) that, when both are set, are applied to every SolrJ request
    via `HttpJdkSolrClient.Builder#withBasicAuthCredentials`. When either
    value is missing or the username is blank, the client is built without
    credentials so existing unauthenticated deployments are unaffected.
    
    Documents the new variables in the Development and Architecture guides
    and adds `SolrConfigAuthTest` covering:
    
    - no credentials attached when both values are missing,
    - no credentials attached when only one value is provided (or the
      username is blank), and
    - credentials applied when both values are provided.
    
    The existing `SolrConfigUrlNormalizationTest` is updated to the new
    record constructor.
    
    Signed-off-by: Yunus Emre Korkmaz <[email protected]>
    Co-authored-by: Claude <[email protected]>
---
 dev-docs/ARCHITECTURE.md                           |  2 +
 dev-docs/DEVELOPMENT.md                            |  2 +
 .../apache/solr/mcp/server/config/SolrConfig.java  | 15 +++-
 .../server/config/SolrConfigurationProperties.java | 20 ++++-
 .../solr/mcp/server/config/SolrConfigAuthTest.java | 96 ++++++++++++++++++++++
 .../config/SolrConfigUrlNormalizationTest.java     |  2 +-
 6 files changed, 133 insertions(+), 4 deletions(-)

diff --git a/dev-docs/ARCHITECTURE.md b/dev-docs/ARCHITECTURE.md
index eae59f0..f379d77 100644
--- a/dev-docs/ARCHITECTURE.md
+++ b/dev-docs/ARCHITECTURE.md
@@ -165,6 +165,8 @@ All dependencies are managed via Gradle version catalogs in 
`gradle/libs.version
 
 ### Solr Connection
 - `SOLR_URL`: Solr instance URL (default: `http://localhost:8983/solr/`)
+- `SOLR_USERNAME`: HTTP Basic Authentication username (optional; required 
together with `SOLR_PASSWORD`)
+- `SOLR_PASSWORD`: HTTP Basic Authentication password (optional; required 
together with `SOLR_USERNAME`)
 
 ### Transport Mode
 - `PROFILES`: Set to `stdio` or `http` to select transport mode
diff --git a/dev-docs/DEVELOPMENT.md b/dev-docs/DEVELOPMENT.md
index 0cb766a..a3909e5 100644
--- a/dev-docs/DEVELOPMENT.md
+++ b/dev-docs/DEVELOPMENT.md
@@ -132,6 +132,8 @@ The server will start on http://localhost:8080
 ### Environment Variables
 
 - `SOLR_URL`: Solr instance URL (default: `http://localhost:8983/solr/`)
+- `SOLR_USERNAME`: HTTP Basic Authentication username (optional; required 
together with `SOLR_PASSWORD`)
+- `SOLR_PASSWORD`: HTTP Basic Authentication password (optional; required 
together with `SOLR_USERNAME`)
 - `PROFILES`: Transport mode (`stdio` or `http`)
 - `SPRING_DOCKER_COMPOSE_ENABLED`: Enable/disable Docker Compose integration 
(default: `true`)
 
diff --git a/src/main/java/org/apache/solr/mcp/server/config/SolrConfig.java 
b/src/main/java/org/apache/solr/mcp/server/config/SolrConfig.java
index db96740..d0eb1da 100644
--- a/src/main/java/org/apache/solr/mcp/server/config/SolrConfig.java
+++ b/src/main/java/org/apache/solr/mcp/server/config/SolrConfig.java
@@ -199,8 +199,19 @@ public class SolrConfig {
                // additional reflection metadata in GraalVM native images.
                // Force HTTP/1.1: the JDK HttpClient's HTTP/2 transport 
intermittently
                // closes reused connections with an EOFException against 
Solr/Jetty.
-               return new 
HttpJdkSolrClient.Builder(url).withConnectionTimeout(CONNECTION_TIMEOUT_MS, 
TimeUnit.MILLISECONDS)
+               HttpJdkSolrClient.Builder builder = new 
HttpJdkSolrClient.Builder(url)
+                               .withConnectionTimeout(CONNECTION_TIMEOUT_MS, 
TimeUnit.MILLISECONDS)
                                .withIdleTimeout(SOCKET_TIMEOUT_MS, 
TimeUnit.MILLISECONDS).useHttp1_1(true)
-                               
.withResponseParser(jsonResponseParser).withRequestWriter(new 
XMLRequestWriter()).build();
+                               
.withResponseParser(jsonResponseParser).withRequestWriter(new 
XMLRequestWriter());
+
+               // Optional HTTP Basic Authentication: applied only when both 
credentials
+               // are provided so existing unauthenticated deployments are 
unaffected.
+               String username = properties.username();
+               String password = properties.password();
+               if (username != null && !username.isEmpty() && password != 
null) {
+                       builder.withBasicAuthCredentials(username, password);
+               }
+
+               return builder.build();
        }
 }
diff --git 
a/src/main/java/org/apache/solr/mcp/server/config/SolrConfigurationProperties.java
 
b/src/main/java/org/apache/solr/mcp/server/config/SolrConfigurationProperties.java
index 37d458b..a0e1535 100644
--- 
a/src/main/java/org/apache/solr/mcp/server/config/SolrConfigurationProperties.java
+++ 
b/src/main/java/org/apache/solr/mcp/server/config/SolrConfigurationProperties.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.mcp.server.config;
 
+import org.jspecify.annotations.Nullable;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 
 /**
@@ -107,12 +108,29 @@ import 
org.springframework.boot.context.properties.ConfigurationProperties;
  * validation and normalization occurs in the {@link SolrConfig} class during
  * SolrClient bean creation.
  *
+ * <p>
+ * <strong>Optional Basic Authentication:</strong>
+ *
+ * <p>
+ * When the target Solr instance requires HTTP Basic Authentication, set both
+ * {@code solr.username} and {@code solr.password} (or the corresponding
+ * {@code SOLR_USERNAME} / {@code SOLR_PASSWORD} environment variables).
+ * Credentials are applied on every request by the configured SolrJ client. If
+ * either value is missing or {@code username} is blank, no authentication
+ * header is attached and the client behaves as before.
+ *
  * @param url
  *            the base URL of the Apache Solr server (required, non-null)
+ * @param username
+ *            the HTTP Basic Authentication username (optional; required
+ *            together with {@code password} to enable auth)
+ * @param password
+ *            the HTTP Basic Authentication password (optional; required
+ *            together with {@code username} to enable auth)
  * @see SolrConfig
  * @see org.springframework.boot.context.properties.ConfigurationProperties
  * @see 
org.springframework.boot.context.properties.EnableConfigurationProperties
  */
 @ConfigurationProperties(prefix = "solr")
-public record SolrConfigurationProperties(String url) {
+public record SolrConfigurationProperties(String url, @Nullable String 
username, @Nullable String password) {
 }
diff --git 
a/src/test/java/org/apache/solr/mcp/server/config/SolrConfigAuthTest.java 
b/src/test/java/org/apache/solr/mcp/server/config/SolrConfigAuthTest.java
new file mode 100644
index 0000000..8a49ded
--- /dev/null
+++ b/src/test/java/org/apache/solr/mcp/server/config/SolrConfigAuthTest.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.mcp.server.config;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.lang.reflect.Field;
+import java.util.Base64;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.impl.HttpJdkSolrClient;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.json.JsonTest;
+
+/**
+ * Unit tests for the optional HTTP Basic Authentication wiring performed by
+ * {@link SolrConfig}.
+ *
+ * <p>
+ * Verifies that credentials are only attached to the SolrJ client when both
+ * {@code username} and {@code password} are provided, and that the resulting
+ * {@code Authorization} header string matches the expected Basic scheme
+ * encoding.
+ */
+@JsonTest
+class SolrConfigAuthTest {
+
+       @Autowired
+       private ObjectMapper objectMapper;
+
+       @Test
+       void noCredentialsWhenBothMissing() throws Exception {
+               assertNull(authorizationHeaderFor(null, null));
+       }
+
+       @ParameterizedTest
+       @CsvSource({"alice,", ",secret", "'',secret"})
+       void noCredentialsWhenEitherValueIsMissingOrBlank(String username, 
String password) throws Exception {
+               assertNull(authorizationHeaderFor(username, password));
+       }
+
+       @Test
+       void credentialsAppliedWhenBothProvided() throws Exception {
+               String header = authorizationHeaderFor("alice", "s3cret");
+               assertNotNull(header);
+               String expected = 
Base64.getEncoder().encodeToString("alice:s3cret".getBytes());
+               assertTrue(header.equals(expected) || header.equals("Basic " + 
expected),
+                               "Expected Basic auth payload for alice:s3cret, 
got: " + header);
+       }
+
+       private @org.jspecify.annotations.Nullable String 
authorizationHeaderFor(
+                       @org.jspecify.annotations.Nullable String username, 
@org.jspecify.annotations.Nullable String password)
+                       throws Exception {
+               SolrConfigurationProperties properties = new 
SolrConfigurationProperties("http://localhost:8983/solr/";,
+                               username, password);
+               SolrConfig config = new SolrConfig();
+               try (SolrClient client = config.solrClient(properties, new 
JsonResponseParser(objectMapper))) {
+                       HttpJdkSolrClient httpClient = 
assertInstanceOf(HttpJdkSolrClient.class, client);
+                       Field field = findField(httpClient.getClass(), 
"basicAuthAuthorizationStr");
+                       field.setAccessible(true);
+                       return (String) field.get(httpClient);
+               }
+       }
+
+       private static Field findField(Class<?> type, String name) throws 
NoSuchFieldException {
+               Class<?> current = type;
+               while (current != null) {
+                       try {
+                               return current.getDeclaredField(name);
+                       } catch (NoSuchFieldException ignored) {
+                               current = current.getSuperclass();
+                       }
+               }
+               throw new NoSuchFieldException(name);
+       }
+}
diff --git 
a/src/test/java/org/apache/solr/mcp/server/config/SolrConfigUrlNormalizationTest.java
 
b/src/test/java/org/apache/solr/mcp/server/config/SolrConfigUrlNormalizationTest.java
index 2ca312f..4bcd222 100644
--- 
a/src/test/java/org/apache/solr/mcp/server/config/SolrConfigUrlNormalizationTest.java
+++ 
b/src/test/java/org/apache/solr/mcp/server/config/SolrConfigUrlNormalizationTest.java
@@ -39,7 +39,7 @@ class SolrConfigUrlNormalizationTest {
                        "http://localhost:8983/solr/, 
http://localhost:8983/solr";,
                        "http://localhost:8983/custom/solr/, 
http://localhost:8983/custom/solr"})
        void testUrlNormalization(String inputUrl, String expectedUrl) throws 
Exception {
-               SolrConfigurationProperties testProperties = new 
SolrConfigurationProperties(inputUrl);
+               SolrConfigurationProperties testProperties = new 
SolrConfigurationProperties(inputUrl, null, null);
                SolrConfig solrConfig = new SolrConfig();
 
                try (SolrClient client = solrConfig.solrClient(testProperties, 
new JsonResponseParser(objectMapper))) {

Reply via email to