diff --git a/.github/workflows/dist-win.yml b/.github/workflows/dist-win.yml
index d3b94d056df..f575b57668f 100644
--- a/.github/workflows/dist-win.yml
+++ b/.github/workflows/dist-win.yml
@@ -30,10 +30,10 @@ jobs:
with:
ref: ${{ inputs.ref || github.ref }}
- - name: Set up JDK 21 (x64)
+ - name: Set up JDK 25 (x64)
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
- java-version: 21
+ java-version: 25
distribution: 'temurin'
architecture: x64
cache: maven
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 74f9c8ba1e3..aa014613be8 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -21,7 +21,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
- jdk: [21]
+ jdk: [25]
runs-on: ${{ matrix.os }}
diff --git a/inception/inception-dependencies/pom.xml b/inception/inception-dependencies/pom.xml
index b889648b0b1..9ea6d900c97 100644
--- a/inception/inception-dependencies/pom.xml
+++ b/inception/inception-dependencies/pom.xml
@@ -1334,6 +1334,11 @@
+
+ org.eclipse.rdf4j
+ rdf4j-http-client-api
+ ${rdf4j.version}
+
org.eclipse.rdf4j
rdf4j-rio-api
diff --git a/inception/inception-kb/pom.xml b/inception/inception-kb/pom.xml
index 84fe5d825fd..e54f2c6f13a 100644
--- a/inception/inception-kb/pom.xml
+++ b/inception/inception-kb/pom.xml
@@ -124,6 +124,10 @@
org.eclipse.rdf4j
rdf4j-repository-manager
+
+ org.eclipse.rdf4j
+ rdf4j-http-client-api
+
org.eclipse.rdf4j
rdf4j-sail-lucene-api
@@ -274,16 +278,18 @@
com.fasterxml.jackson.core
jackson-annotations
+
+
org.apache.httpcomponents
httpclient
+ test
org.apache.httpcomponents
httpcore
+ test
-
-
de.tudarmstadt.ukp.inception.app
inception-layer-span-api
diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImpl.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImpl.java
index cf0e60b30ec..5d6768772d7 100644
--- a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImpl.java
+++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImpl.java
@@ -18,6 +18,7 @@
package de.tudarmstadt.ukp.inception.kb;
import static de.tudarmstadt.ukp.inception.kb.RepositoryType.LOCAL;
+import static de.tudarmstadt.ukp.inception.kb.RepositoryType.REMOTE;
import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.restoreSslVerification;
import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.skipCertificateChecks;
import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilder.DEFAULT_LIMIT;
@@ -227,8 +228,8 @@ public KnowledgeBaseServiceImpl(RepositoryProperties aRepoProperties,
}
repoManager = RepositoryProvider.getRepositoryManager(kbRepositoriesRoot);
- repoManager.setHttpClient(PerThreadSslCheckingHttpClientUtils
- .newPerThreadSslCheckingHttpClientBuilder().build());
+ repoManager.setHttpClient(
+ PerThreadSslCheckingHttpClientUtils.newPerThreadSslCheckingHttpClient());
BOOT_LOG.info("Knowledge base repository path: {}", kbRepositoriesRoot);
}
@@ -273,6 +274,9 @@ void onContextRefreshed()
if (LOCAL == kb.getType()) {
reconfigureLocalKnowledgeBase(kb);
}
+ else if (REMOTE == kb.getType()) {
+ migrateUrlEmbeddedCredentials(kb);
+ }
}
if (!orphanedIDs.isEmpty()) {
@@ -700,7 +704,14 @@ public RepositoryImplConfig getNativeConfig()
@Override
public RepositoryImplConfig getRemoteConfig(String url)
{
- return new SPARQLRepositoryConfig(url);
+ var split = splitUrlUserInfo(url);
+ if (split.userInfo() != null) {
+ LOG.warn(
+ "URL [{}] contains embedded credentials. Stripping them - configure "
+ + "authentication via the KB auth-traits UI instead.",
+ split.cleanUrl());
+ }
+ return new SPARQLRepositoryConfig(split.cleanUrl());
}
@Override
@@ -833,25 +844,100 @@ private void addAdditionalHeaders(SPARQLRepository aSparqlRepo, Map true);
-
- HostnameVerifier hostNameVerifier = (String hostname,
- SSLSession session) -> true;
- factoryWithoutSslChecks = new SSLConnectionSocketFactory(builder.build(),
- hostNameVerifier);
- }
- catch (Exception e) {
- // key management exception, etc.
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public Socket createSocket(HttpContext aContext) throws IOException
- {
- LOG.trace("createSocket (SSL checks: {})", SSL_VERIFICATION_ENABLED.get().peek());
- if (SSL_VERIFICATION_ENABLED.get().peek()) {
- return factoryWithChecks.createSocket(aContext);
- }
- else {
- return factoryWithoutSslChecks.createSocket(aContext);
- }
- }
-
- @Override
- public Socket connectSocket(int aConnectTimeout, Socket aSock, HttpHost aHost,
- InetSocketAddress aRemoteAddress, InetSocketAddress aLocalAddress,
- HttpContext aContext)
- throws IOException
+ try {
+ var ctx = SSLContext.getInstance("TLS");
+ ctx.init(null, new TrustManager[] { new X509TrustManager()
{
- LOG.trace("connectSocket (SSL checks: {})", SSL_VERIFICATION_ENABLED.get().peek());
- if (SSL_VERIFICATION_ENABLED.get().peek()) {
- return factoryWithChecks.connectSocket(aConnectTimeout, aSock, aHost,
- aRemoteAddress, aLocalAddress, aContext);
- }
- else {
- return factoryWithoutSslChecks.connectSocket(aConnectTimeout, aSock, aHost,
- aRemoteAddress, aLocalAddress, aContext);
+ @Override
+ public void checkClientTrusted(X509Certificate[] aChain, String aAuthType)
+ {
}
- }
- @Override
- public Socket createLayeredSocket(Socket aSocket, String aTarget, int aPort,
- HttpContext aContext)
- throws IOException, UnknownHostException
- {
- LOG.trace("createLayeredSocket (SSL checks: {})",
- SSL_VERIFICATION_ENABLED.get().peek());
- if (SSL_VERIFICATION_ENABLED.get().peek()) {
- return factoryWithChecks.createLayeredSocket(aSocket, aTarget, aPort, aContext);
- }
- else {
- return factoryWithoutSslChecks.createLayeredSocket(aSocket, aTarget, aPort,
- aContext);
+ @Override
+ public void checkServerTrusted(X509Certificate[] aChain, String aAuthType)
+ {
}
- }
- };
- }
-
- private static UserTokenHandler newCertCheckAwareUserTokenHandler()
- {
- return new DefaultUserTokenHandler()
- {
- @Override
- public Object getUserToken(HttpContext aContext)
- {
- LOG.trace("getUserToken (SSL checks: {})", SSL_VERIFICATION_ENABLED.get().peek());
- if (SSL_VERIFICATION_ENABLED.get().peek()) {
- return super.getUserToken(aContext);
- }
- else {
- return new WrappedUserToken(super.getUserToken(aContext),
- SSL_VERIFICATION_ENABLED.get().peek());
+ @Override
+ public X509Certificate[] getAcceptedIssuers()
+ {
+ return new X509Certificate[0];
}
- }
- };
- }
-
- /**
- * Return an {@link HttpClientBuilder} that can be used to build an {@link HttpClient} which
- * trusts all certificates (particularly including self-signed certificates).
- *
- * @return a {@link HttpClientBuilder} for SSL trust all
- */
- public static HttpClientBuilder newPerThreadSslCheckingHttpClientBuilder()
- {
- return HttpClients.custom() //
- // Need to inject the certificate checking state into the "user token" otherwise
- // HTTP connections with checking and without checking will be considered as
- // equivalent by the connection pool used by the HTTPClient
- .setUserTokenHandler(newCertCheckAwareUserTokenHandler()) //
- .setSSLSocketFactory(newCertCheckAwareSSLConnectionSocketFactory()) //
- .useSystemProperties();
+ } }, null);
+ return ctx;
+ }
+ catch (GeneralSecurityException e) {
+ throw new RuntimeException(e);
+ }
}
- public static class WrappedUserToken
+ private static final class PerThreadSslCheckingRdf4jHttpClient
+ implements RDF4JHttpClient
{
- private final Object userToken;
- private final boolean sslCheckSkipped;
-
- public WrappedUserToken(Object aUserToken, boolean aSslCheckSkipped)
- {
- super();
- userToken = aUserToken;
- sslCheckSkipped = aSslCheckSkipped;
- }
-
- public Object getUserToken()
- {
- return userToken;
- }
+ private final RDF4JHttpClient strictClient;
+ private final RDF4JHttpClient trustAllClient;
- public boolean isSslCheckSkipped()
+ PerThreadSslCheckingRdf4jHttpClient()
{
- return sslCheckSkipped;
+ strictClient = RDF4JHttpClients.newDefaultClient();
+ trustAllClient = RDF4JHttpClients.newDefaultClient(RDF4JHttpClientConfig.newBuilder() //
+ .sslContext(newTrustAllSslContext()) //
+ .disableHostnameVerification(true) //
+ .build());
}
@Override
- public boolean equals(final Object other)
+ public HttpResponse execute(HttpRequest aRequest) throws IOException
{
- if (!(other instanceof WrappedUserToken)) {
- return false;
- }
- WrappedUserToken castOther = (WrappedUserToken) other;
- return Objects.equals(userToken, castOther.userToken)
- && Objects.equals(sslCheckSkipped, castOther.sslCheckSkipped);
+ boolean checksEnabled = SSL_VERIFICATION_ENABLED.get().peek();
+ LOG.trace("execute (SSL checks: {})", checksEnabled);
+ return (checksEnabled ? strictClient : trustAllClient).execute(aRequest);
}
@Override
- public int hashCode()
+ public void close()
{
- return Objects.hash(userToken, sslCheckSkipped);
+ try {
+ strictClient.close();
+ }
+ finally {
+ trustAllClient.close();
+ }
}
}
diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/FullTextIndexUpgradeTest.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/FullTextIndexUpgradeTest.java
index 52f9c9bf7aa..959e55f89f1 100644
--- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/FullTextIndexUpgradeTest.java
+++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/FullTextIndexUpgradeTest.java
@@ -235,6 +235,21 @@ void thatIndexCanBeUpgradedInPlace() throws Exception
"http://www.ukp.informatik.tu-darmstadt.de/inception/1.0#green-goblin");
}
+ @Test
+ void thatTooOldIndexVersionIsReportedAsSuch() throws Exception
+ {
+ copyDirectory(new File(REF_DIR, "lucene-8.11.3"), temp);
+
+ sut = new KnowledgeBaseServiceImpl(repoProperties, kbProperties, entityManager);
+
+ kb.setRepositoryId("pid-1-kbid-");
+ sut.reconfigureLocalKnowledgeBase(kb);
+
+ assertThat(sut.getIndexVersion(kb))
+ .hasValueSatisfying(v -> assertThat(v).startsWith("8.").contains("too old"));
+ assertThat(sut.isIndexUpgradeAvailable(kb)).isFalse();
+ }
+
@Test
void thatRebuildRecoversFromIndexFormatTooOld() throws Exception
{
diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilderLocalTestScenarios.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilderLocalTestScenarios.java
index 9dced05fe17..c73dc381c4c 100644
--- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilderLocalTestScenarios.java
+++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilderLocalTestScenarios.java
@@ -17,7 +17,7 @@
*/
package de.tudarmstadt.ukp.inception.kb.querybuilder;
-import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.newPerThreadSslCheckingHttpClientBuilder;
+import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.newPerThreadSslCheckingHttpClient;
import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.restoreSslVerification;
import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.suspendSslVerification;
import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderAsserts.asHandle;
@@ -38,6 +38,7 @@
import java.io.InputStream;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
+import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -367,8 +368,10 @@ public Scenario(String aName,
static Repository buildSparqlRepository(String aUrl)
{
- var repo = new SPARQLRepository(aUrl);
- repo.setHttpClient(newPerThreadSslCheckingHttpClientBuilder().build());
+ var creds = extractCredentials(aUrl);
+ var repo = new SPARQLRepository(creds.urlWithoutUserInfo());
+ applyCredentials(repo, creds);
+ repo.setHttpClient(newPerThreadSslCheckingHttpClient());
repo.setAdditionalHttpHeaders(Map.of("User-Agent", "INCEpTION/0.0.1-SNAPSHOT"));
repo.init();
return repo;
@@ -376,13 +379,42 @@ static Repository buildSparqlRepository(String aUrl)
static Repository buildSparqlRepository(String aQueryUrl, String aUpdateUrl)
{
- var repo = new SPARQLRepository(aQueryUrl, aUpdateUrl);
- repo.setHttpClient(newPerThreadSslCheckingHttpClientBuilder().build());
+ var queryCreds = extractCredentials(aQueryUrl);
+ var updateCreds = extractCredentials(aUpdateUrl);
+ var repo = new SPARQLRepository(queryCreds.urlWithoutUserInfo(),
+ updateCreds.urlWithoutUserInfo());
+ applyCredentials(repo, queryCreds);
+ repo.setHttpClient(newPerThreadSslCheckingHttpClient());
repo.setAdditionalHttpHeaders(Map.of("User-Agent", "INCEpTION/0.0.1-SNAPSHOT"));
repo.init();
return repo;
}
+ private record UrlCredentials(String urlWithoutUserInfo, String user, String password) {}
+
+ // Apache HttpClient 5 (used by RDF4J 6's default HTTP client) rejects URIs with a userinfo
+ // component. Split user:pass@ out of the URL and pass it via setUsernameAndPassword instead.
+ private static UrlCredentials extractCredentials(String aUrl)
+ {
+ var uri = URI.create(aUrl);
+ var userInfo = uri.getRawUserInfo();
+ if (userInfo == null) {
+ return new UrlCredentials(aUrl, null, null);
+ }
+ var sep = userInfo.indexOf(':');
+ var user = sep < 0 ? userInfo : userInfo.substring(0, sep);
+ var password = sep < 0 ? "" : userInfo.substring(sep + 1);
+ var stripped = aUrl.replace(userInfo + "@", "");
+ return new UrlCredentials(stripped, user, password);
+ }
+
+ private static void applyCredentials(SPARQLRepository aRepo, UrlCredentials aCreds)
+ {
+ if (aCreds.user() != null) {
+ aRepo.setUsernameAndPassword(aCreds.user(), aCreds.password());
+ }
+ }
+
/**
* Checks that {@code SPARQLQueryBuilder#exists(RepositoryConnection, boolean)} can return
* {@code true} by querying for a list of all classes in {@link #DATA_CLASS_RDFS_HIERARCHY}
diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/util/TestFixtures.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/util/TestFixtures.java
index 31b3a8eb8c2..152850061fa 100644
--- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/util/TestFixtures.java
+++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/util/TestFixtures.java
@@ -19,7 +19,7 @@
import static de.tudarmstadt.ukp.inception.kb.IriConstants.FTS_RDF4J_LUCENE;
import static de.tudarmstadt.ukp.inception.kb.IriConstants.INCEPTION_NAMESPACE;
-import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.newPerThreadSslCheckingHttpClientBuilder;
+import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.newPerThreadSslCheckingHttpClient;
import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
import static java.net.HttpURLConnection.HTTP_MOVED_TEMP;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@@ -151,7 +151,7 @@ public static Repository buildRepository(KnowledgeBaseProfile profile) throws IO
+ profile.getAccess().getAccessUrl() + "] is not reachable");
var repo = new SPARQLRepository(profile.getAccess().getAccessUrl());
- repo.setHttpClient(newPerThreadSslCheckingHttpClientBuilder().build());
+ repo.setHttpClient(newPerThreadSslCheckingHttpClient());
repo.setAdditionalHttpHeaders(Map.of("User-Agent", "INCEpTION/0.0.1-SNAPSHOT"));
repo.init();
return repo;
@@ -264,7 +264,7 @@ public static boolean isReachable(String aUrl)
}
var r = new SPARQLRepository(aUrl);
- r.setHttpClient(newPerThreadSslCheckingHttpClientBuilder().build());
+ r.setHttpClient(newPerThreadSslCheckingHttpClient());
r.setAdditionalHttpHeaders(Map.of("User-Agent", "INCEpTION/0.0.1-SNAPSHOT"));
r.init();
try (var conn = r.getConnection()) {
diff --git a/inception/pom.xml b/inception/pom.xml
index 775278e397a..0ea126e31bf 100644
--- a/inception/pom.xml
+++ b/inception/pom.xml
@@ -29,7 +29,7 @@
6g
-XX:+EnableDynamicAgentLoading
- 21
+ 25
${maven.compiler.release}
${maven.compiler.release}
yyyy-MM-dd HH:mm
diff --git a/pom.xml b/pom.xml
index dcbb02eb9ea..4e956a1fe1a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -133,7 +133,7 @@
3.6.0
6.1.0
0.23.0
- 5.3.1
+ 6.0.0-M2
5.5.1
3.2.0