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))) {