|
18 | 18 | package de.tudarmstadt.ukp.inception.kb; |
19 | 19 |
|
20 | 20 | import static de.tudarmstadt.ukp.inception.kb.RepositoryType.LOCAL; |
| 21 | +import static de.tudarmstadt.ukp.inception.kb.RepositoryType.REMOTE; |
21 | 22 | import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.restoreSslVerification; |
22 | 23 | import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.skipCertificateChecks; |
23 | 24 | import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilder.DEFAULT_LIMIT; |
@@ -273,6 +274,9 @@ void onContextRefreshed() |
273 | 274 | if (LOCAL == kb.getType()) { |
274 | 275 | reconfigureLocalKnowledgeBase(kb); |
275 | 276 | } |
| 277 | + else if (REMOTE == kb.getType()) { |
| 278 | + migrateUrlEmbeddedCredentials(kb); |
| 279 | + } |
276 | 280 | } |
277 | 281 |
|
278 | 282 | if (!orphanedIDs.isEmpty()) { |
@@ -700,7 +704,14 @@ public RepositoryImplConfig getNativeConfig() |
700 | 704 | @Override |
701 | 705 | public RepositoryImplConfig getRemoteConfig(String url) |
702 | 706 | { |
703 | | - return new SPARQLRepositoryConfig(url); |
| 707 | + var split = splitUrlUserInfo(url); |
| 708 | + if (split.userInfo() != null) { |
| 709 | + LOG.warn( |
| 710 | + "URL [{}] contains embedded credentials. Stripping them - configure " |
| 711 | + + "authentication via the KB auth-traits UI instead.", |
| 712 | + split.cleanUrl()); |
| 713 | + } |
| 714 | + return new SPARQLRepositoryConfig(split.cleanUrl()); |
704 | 715 | } |
705 | 716 |
|
706 | 717 | @Override |
@@ -833,25 +844,100 @@ private void addAdditionalHeaders(SPARQLRepository aSparqlRepo, Map<String, Stri |
833 | 844 | private void applyBasicHttpAuthenticationConfigurationFromUrl( |
834 | 845 | SPARQLRepositoryConfig sparqlRepoConfig, SPARQLRepository sparqlRepo) |
835 | 846 | { |
836 | | - var uri = URI.create(sparqlRepoConfig.getQueryEndpointUrl()); |
837 | | - var userInfo = uri.getUserInfo(); |
838 | | - if (isNotBlank(userInfo)) { |
839 | | - userInfo = userInfo.trim(); |
840 | | - String username; |
841 | | - String password; |
842 | | - if (userInfo.contains(":")) { |
843 | | - username = substringBefore(userInfo, ":"); |
844 | | - password = substringAfter(userInfo, ":"); |
| 847 | + var split = splitUrlUserInfo(sparqlRepoConfig.getQueryEndpointUrl()); |
| 848 | + if (split.user() != null) { |
| 849 | + sparqlRepo.setUsernameAndPassword(split.user(), split.password()); |
| 850 | + } |
| 851 | + } |
| 852 | + |
| 853 | + /** |
| 854 | + * Migrates URL-embedded credentials ({@code http://user:pass@host/...}) on a REMOTE KB into |
| 855 | + * {@link BasicAuthenticationTraits} and a cleaned URL. Apache HttpClient 5 (used by RDF4J 6) |
| 856 | + * rejects URIs with a userinfo component outright, so legacy configs that worked under RDF4J 5 |
| 857 | + * must be normalized before the next connection attempt. Invoked once per KB at startup. |
| 858 | + */ |
| 859 | + private void migrateUrlEmbeddedCredentials(KnowledgeBase aKB) |
| 860 | + { |
| 861 | + try { |
| 862 | + var cfg = getKnowledgeBaseConfig(aKB); |
| 863 | + if (!(cfg instanceof SPARQLRepositoryConfig sparqlCfg)) { |
| 864 | + return; |
| 865 | + } |
| 866 | + |
| 867 | + var queryUrl = sparqlCfg.getQueryEndpointUrl(); |
| 868 | + var updateUrl = sparqlCfg.getUpdateEndpointUrl(); |
| 869 | + var querySplit = splitUrlUserInfo(queryUrl); |
| 870 | + var updateSplit = splitUrlUserInfo(updateUrl); |
| 871 | + |
| 872 | + if (querySplit.userInfo() == null && updateSplit.userInfo() == null) { |
| 873 | + return; |
| 874 | + } |
| 875 | + |
| 876 | + var newCfg = updateUrl == null // |
| 877 | + ? new SPARQLRepositoryConfig(querySplit.cleanUrl()) // |
| 878 | + : new SPARQLRepositoryConfig(querySplit.cleanUrl(), updateSplit.cleanUrl()); |
| 879 | + |
| 880 | + // Prefer the query URL's credentials; fall back to the update URL's. |
| 881 | + var credSource = querySplit.user() != null ? querySplit : updateSplit; |
| 882 | + |
| 883 | + var traits = isNotBlank(aKB.getTraits()) ? readTraits(aKB) : null; |
| 884 | + if (traits == null) { |
| 885 | + traits = new RemoteRepositoryTraits(); |
| 886 | + } |
| 887 | + var hadAuth = traits.getAuthentication() != null; |
| 888 | + if (!hadAuth && credSource.user() != null) { |
| 889 | + var basic = new BasicAuthenticationTraits(); |
| 890 | + basic.setUsername(credSource.user()); |
| 891 | + basic.setPassword(credSource.password()); |
| 892 | + traits.setAuthentication(basic); |
| 893 | + aKB.setTraits(JSONUtil.toJsonString(traits)); |
| 894 | + } |
| 895 | + |
| 896 | + updateKnowledgeBase(aKB, newCfg); |
| 897 | + |
| 898 | + if (hadAuth) { |
| 899 | + LOG.info( |
| 900 | + "Migrated KB [{}]: stripped URL-embedded credentials " |
| 901 | + + "(KB already had explicit auth traits configured).", |
| 902 | + aKB.getName()); |
845 | 903 | } |
846 | 904 | else { |
847 | | - username = userInfo; |
848 | | - password = ""; |
| 905 | + LOG.info("Migrated KB [{}]: moved URL-embedded credentials into " |
| 906 | + + "basic-auth traits.", aKB.getName()); |
849 | 907 | } |
| 908 | + } |
| 909 | + catch (Exception e) { |
| 910 | + LOG.error( |
| 911 | + "Unable to migrate URL-embedded credentials for KB [{}]. " |
| 912 | + + "Remote connections may fail until the URL is corrected manually.", |
| 913 | + aKB.getName(), e); |
| 914 | + } |
| 915 | + } |
850 | 916 |
|
851 | | - sparqlRepo.setUsernameAndPassword(username, password); |
| 917 | + /** |
| 918 | + * Parses a URL, returning the userinfo (or {@code null}) and the URL with the userinfo |
| 919 | + * stripped. Returns {@code (cleanUrl=null, user=null, password=null, userInfo=null)} for a |
| 920 | + * {@code null} input URL. |
| 921 | + */ |
| 922 | + private static UrlUserInfo splitUrlUserInfo(String aUrl) |
| 923 | + { |
| 924 | + if (aUrl == null) { |
| 925 | + return new UrlUserInfo(null, null, null, null); |
852 | 926 | } |
| 927 | + var uri = URI.create(aUrl); |
| 928 | + var userInfo = uri.getUserInfo(); |
| 929 | + if (!isNotBlank(userInfo)) { |
| 930 | + return new UrlUserInfo(aUrl, null, null, null); |
| 931 | + } |
| 932 | + userInfo = userInfo.trim(); |
| 933 | + var user = userInfo.contains(":") ? substringBefore(userInfo, ":") : userInfo; |
| 934 | + var password = userInfo.contains(":") ? substringAfter(userInfo, ":") : ""; |
| 935 | + var cleanUrl = aUrl.replace(uri.getRawUserInfo() + "@", ""); |
| 936 | + return new UrlUserInfo(cleanUrl, user, password, userInfo); |
853 | 937 | } |
854 | 938 |
|
| 939 | + private record UrlUserInfo(String cleanUrl, String user, String password, String userInfo) {} |
| 940 | + |
855 | 941 | @SuppressWarnings("resource") |
856 | 942 | @Override |
857 | 943 | public void importData(KnowledgeBase kb, String aFilename, InputStream aIS) |
|
0 commit comments