Skip to content

Commit 2945a4e

Browse files
Merge pull request #4445 from AfraHussaindeen/4.10.x_ldaps-fix
Implement port resolution for LDAP URLs to add default ports if not specified
2 parents 5f22695 + 9948b94 commit 2945a4e

File tree

3 files changed

+151
-15
lines changed

3 files changed

+151
-15
lines changed

core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/ldap/LDAPConnectionContext.java

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -144,21 +144,7 @@ public LDAPConnectionContext(RealmConfiguration realmConfig) throws UserStoreExc
144144
String connectionURL = null;
145145
//if DNS enabled in AD case, this can be null
146146
if (rawConnectionURL != null) {
147-
String portInfo = rawConnectionURL.split(":")[2];
148-
149-
String port = null;
150-
151-
// if the port contains a template string that refers to carbon.xml
152-
if ((portInfo.contains("${")) && (portInfo.contains("}"))) {
153-
port = Integer.toString(CarbonUtils.getPortFromServerConfig(portInfo));
154-
}
155-
156-
if (port != null) {
157-
connectionURL = rawConnectionURL.replace(portInfo, port);
158-
} else {
159-
// if embedded-ldap is not enabled,
160-
connectionURL = realmConfig.getUserStoreProperty(LDAPConstants.CONNECTION_URL);
161-
}
147+
connectionURL = resolvePort(rawConnectionURL);
162148
}
163149

164150
String connectionName = realmConfig.getUserStoreProperty(LDAPConstants.CONNECTION_NAME);
@@ -1123,4 +1109,65 @@ protected long getValidatedThresholdTimeoutInMilliseconds(long retryWaitingTime)
11231109
throw new UserStoreException("Error occurred while parsing ConnectionRetryDelay property value.");
11241110
}
11251111
}
1112+
1113+
/**
1114+
* Resolves the port for the connection URL.
1115+
* The port number is optional, according to the LDAP URL spec.
1116+
* Therefore, if the port number isn't present, LDAP URL should default to port 389
1117+
* and LDAPS URL should default to port 636.
1118+
* See spec: https://ldapwiki.com/wiki/Wiki.jsp?page=LDAP+URL
1119+
*
1120+
* @param url The LDAP URL to resolve.
1121+
* @return The URL with resolved port.
1122+
*/
1123+
private static String resolvePort(String url) {
1124+
1125+
if (StringUtils.isEmpty(url)) {
1126+
return url;
1127+
}
1128+
1129+
url = url.trim();
1130+
int schemeIndex = url.indexOf(LDAPConstants.URL_SCHEME_SEPARATOR);
1131+
if (schemeIndex == -1) {
1132+
return url;
1133+
}
1134+
1135+
String scheme = url.substring(0, schemeIndex);
1136+
String rest = url.substring(schemeIndex + LDAPConstants.URL_SCHEME_SEPARATOR.length());
1137+
1138+
String hostAndPort;
1139+
String path = "";
1140+
int pathIndex = rest.indexOf("/");
1141+
if (pathIndex != -1) {
1142+
hostAndPort = rest.substring(0, pathIndex);
1143+
path = rest.substring(pathIndex);
1144+
} else {
1145+
hostAndPort = rest;
1146+
}
1147+
1148+
// Handle IPv6: [2001:db8::1]:389
1149+
int lastColonIndex = hostAndPort.lastIndexOf(':');
1150+
int lastBracketIndex = hostAndPort.lastIndexOf(']');
1151+
1152+
if (lastColonIndex != -1 && lastColonIndex > lastBracketIndex) {
1153+
// Port is present.
1154+
String portInfo = hostAndPort.substring(lastColonIndex + 1);
1155+
// If the port contains a template string that refers to carbon.xml.
1156+
if (portInfo.contains("${") && portInfo.contains("}")) {
1157+
int resolvedPort = CarbonUtils.getPortFromServerConfig(portInfo);
1158+
if (resolvedPort != -1) {
1159+
return scheme + LDAPConstants.URL_SCHEME_SEPARATOR + hostAndPort.substring(0, lastColonIndex) +
1160+
":" + resolvedPort + path;
1161+
}
1162+
}
1163+
return url;
1164+
} else {
1165+
// Port is missing, add default port.
1166+
int defaultPort = LDAPConstants.LDAP_DEFAULT_PORT;
1167+
if (LDAPConstants.LDAPS_SCHEME.equalsIgnoreCase(scheme)) {
1168+
defaultPort = LDAPConstants.LDAPS_DEFAULT_PORT;
1169+
}
1170+
return scheme + LDAPConstants.URL_SCHEME_SEPARATOR + hostAndPort + ":" + defaultPort + path;
1171+
}
1172+
}
11261173
}

core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/ldap/LDAPConstants.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ public class LDAPConstants {
9999
public static final String SHARED_TENANT_OBJECT_CLASS = "SharedTenantObjectClass";
100100

101101
public static final String CONNECTION_POOLING_ENABLED = "ConnectionPoolingEnabled";
102+
103+
public static final int LDAP_DEFAULT_PORT = 389;
104+
public static final int LDAPS_DEFAULT_PORT = 636;
105+
public static final String LDAPS_SCHEME = "ldaps";
106+
public static final String URL_SCHEME_SEPARATOR = "://";
102107
public static final String USER_CACHE_EXPIRY_MILLISECONDS = "UserCacheExpiryMilliseconds";
103108
public static final String USER_DN_CACHE_ENABLED = "UserDNCacheEnabled";
104109

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
3+
*
4+
* WSO2 LLC. licenses this file to you under the Apache License,
5+
* Version 2.0 (the "License"); you may not use this file except
6+
* in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
19+
package org.wso2.carbon.user.core.ldap;
20+
21+
import org.mockito.MockedStatic;
22+
import org.mockito.Mockito;
23+
import org.testng.Assert;
24+
import org.testng.annotations.DataProvider;
25+
import org.testng.annotations.Test;
26+
import org.wso2.carbon.utils.CarbonUtils;
27+
28+
import java.lang.reflect.Method;
29+
30+
import static org.mockito.ArgumentMatchers.anyString;
31+
32+
/**
33+
* Unit tests for LDAPConnectionContext class.
34+
*/
35+
public class LDAPConnectionContextTest {
36+
37+
private String invokeResolvePort(String url) throws Exception {
38+
39+
Method method = LDAPConnectionContext.class.getDeclaredMethod("resolvePort", String.class);
40+
method.setAccessible(true);
41+
return (String) method.invoke(null, url);
42+
}
43+
44+
@DataProvider(name = "urlDataProvider")
45+
public Object[][] urlDataProvider() {
46+
47+
return new Object[][]{
48+
{"ldap://localhost:389", "ldap://localhost:389"},
49+
{"ldap://localhost", "ldap://localhost:389"},
50+
{"ldaps://localhost", "ldaps://localhost:636"},
51+
{"ldap://127.0.0.1:389", "ldap://127.0.0.1:389"},
52+
{"ldap://127.0.0.1", "ldap://127.0.0.1:389"},
53+
{"ldaps://127.0.0.1", "ldaps://127.0.0.1:636"},
54+
{"ldap://[2001:db8::1]:389", "ldap://[2001:db8::1]:389"},
55+
{"ldap://[2001:db8::1]", "ldap://[2001:db8::1]:389"},
56+
{"ldap://localhost/dc=wso2,dc=org", "ldap://localhost:389/dc=wso2,dc=org"},
57+
{" ldap://localhost ", "ldap://localhost:389"}
58+
};
59+
}
60+
61+
@Test(dataProvider = "urlDataProvider")
62+
public void testResolvePort(String url, String expectedUrl) throws Exception {
63+
64+
String resolvedUrl = invokeResolvePort(url);
65+
Assert.assertEquals(resolvedUrl, expectedUrl, "URL resolution failed for: " + url);
66+
}
67+
68+
@Test
69+
public void testResolvePortWithTemplate() throws Exception {
70+
71+
String template = "Ports.EmbeddedLDAP.LDAPServerPort";
72+
int portValue = 10389;
73+
74+
try (MockedStatic<CarbonUtils> carbonUtilsMock = Mockito.mockStatic(CarbonUtils.class)) {
75+
carbonUtilsMock.when(() -> CarbonUtils.getPortFromServerConfig(anyString())).thenReturn(portValue);
76+
77+
String url = "ldap://localhost:${" + template + "}";
78+
String expectedUrl = "ldap://localhost:" + portValue;
79+
80+
String resolvedUrl = invokeResolvePort(url);
81+
Assert.assertEquals(resolvedUrl, expectedUrl, "Template resolution failed");
82+
}
83+
}
84+
}

0 commit comments

Comments
 (0)