diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/provider/local/LocalAuthProviderConstants.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/provider/local/LocalAuthProviderConstants.java
index b121b61947..3e260bb150 100644
--- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/provider/local/LocalAuthProviderConstants.java
+++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/provider/local/LocalAuthProviderConstants.java
@@ -21,5 +21,6 @@
public class LocalAuthProviderConstants {
public static final String PROVIDER_ID = "local";
public static final String CRED_USER = "user";
+ public static final String CRED_DISPLAY_NAME = "displayName";
public static final String CRED_PASSWORD = "password";
}
diff --git a/server/bundles/io.cloudbeaver.service.ldap.auth/OSGI-INF/l10n/bundle.properties b/server/bundles/io.cloudbeaver.service.ldap.auth/OSGI-INF/l10n/bundle.properties
index d6918a3638..a0996bff58 100644
--- a/server/bundles/io.cloudbeaver.service.ldap.auth/OSGI-INF/l10n/bundle.properties
+++ b/server/bundles/io.cloudbeaver.service.ldap.auth/OSGI-INF/l10n/bundle.properties
@@ -5,7 +5,7 @@ prop.auth.model.ldap.ldap-port.description = LDAP server port, default is 389
prop.auth.model.ldap.ldap-identifier-attr = User identifier attribute
prop.auth.model.ldap.ldap-identifier-attr.description = LDAP attribute used as a user ID. Will be automatically added to the beginning of the 'User DN' value during authorization if not explicitly specified
prop.auth.model.ldap.ldap-dn = Base Distinguished Name
-prop.auth.model.ldap.ldap-dn.description = Base Distinguished Name applicable for all users, example: dc=myOrg,dc=com. Will be automatically added to the end of the 'User DN' value during authorization if not explicitly specified
+prop.auth.model.ldap.ldap-dn.description = Base Distinguished Name applicable for all users, example: dc=myOrg,dc=com. Will be automatically added to the end of the 'User DN' value during authorization if not explicitly specified. Leave blank if you want to use the root DN.
prop.auth.model.ldap.ldap-bind-user = Bind User DN
prop.auth.model.ldap.ldap-bind-user.description = DN of user, who has permissions to search for users to check access to the application with the specified filter.
prop.auth.model.ldap.ldap-bind-user-pwd = Bind User Password
@@ -16,4 +16,6 @@ prop.auth.model.ldap.user-dn = User DN
prop.auth.model.ldap.user-dn.description = LDAP user name
prop.auth.model.ldap.password = User password
prop.auth.model.ldap.password.description = LDAP user password
+prop.auth.model.ldap.ldap-login = User login parameter
+prop.auth.model.ldap.ldap-login.description = LDAP attribute to be used as the user login. The attribute must be unique. Configuring the bind user is mandatory to use this parameter.
diff --git a/server/bundles/io.cloudbeaver.service.ldap.auth/OSGI-INF/l10n/bundle_ru.properties b/server/bundles/io.cloudbeaver.service.ldap.auth/OSGI-INF/l10n/bundle_ru.properties
index ce6b56819e..fdf8ada433 100644
--- a/server/bundles/io.cloudbeaver.service.ldap.auth/OSGI-INF/l10n/bundle_ru.properties
+++ b/server/bundles/io.cloudbeaver.service.ldap.auth/OSGI-INF/l10n/bundle_ru.properties
@@ -5,7 +5,7 @@ prop.auth.model.ldap.ldap-port.description = Порт LDAP-сервера, по
prop.auth.model.ldap.ldap-identifier-attr = Атрибут идентификатора пользователя
prop.auth.model.ldap.ldap-identifier-attr.description = Атрибут LDAP, используемый в качестве идентификатора пользователя. Будет автоматически добавлен в начало значения 'User DN' при авторизации, если не указан явно
prop.auth.model.ldap.ldap-dn = Базовое отличительное имя
-prop.auth.model.ldap.ldap-dn.description = Базовое отличительное имя, применимое ко всем пользователям, например: dc=myOrg,dc=com. Будет автоматически добавлено в конец значения 'User DN' при авторизации, если не указано явно
+prop.auth.model.ldap.ldap-dn.description = Базовое отличительное имя, применимое ко всем пользователям, например: dc=myOrg,dc=com. Будет автоматически добавлено в конец значения 'User DN' при авторизации, если не указано явно. Оставьте пустым если хотите использовать root DN.
prop.auth.model.ldap.ldap-bind-user = Привязка DN пользователя
prop.auth.model.ldap.ldap-bind-user.description = DN пользователя, который имеет права на поиск пользователей для проверки доступа к приложению с указанным фильтром.
prop.auth.model.ldap.ldap-bind-user-pwd = Связать пароль пользователя
@@ -16,3 +16,5 @@ prop.auth.model.ldap.user-dn = Имя пользователя DN
prop.auth.model.ldap.user-dn.description = LDAP имя пользователя
prop.auth.model.ldap.password = Пароль пользователя
prop.auth.model.ldap.password.description = LDAP пароль пользователя
+prop.auth.model.ldap.ldap-login = Параметр логина
+prop.auth.model.ldap.ldap-login.description = Атрибут LDAP, который будет использоваться в качестве логина пользователя. Атрибут должен быть уникальным. Настройка привязки пользователя обязательна для использования этого параметра.
\ No newline at end of file
diff --git a/server/bundles/io.cloudbeaver.service.ldap.auth/plugin.xml b/server/bundles/io.cloudbeaver.service.ldap.auth/plugin.xml
index 18e0023dab..ed76a9973b 100644
--- a/server/bundles/io.cloudbeaver.service.ldap.auth/plugin.xml
+++ b/server/bundles/io.cloudbeaver.service.ldap.auth/plugin.xml
@@ -27,11 +27,13 @@
features="password" required="false"/>
+
-
+
diff --git a/server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapAuthProvider.java b/server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapAuthProvider.java
index 7bdee38129..cf8fd4e180 100644
--- a/server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapAuthProvider.java
+++ b/server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapAuthProvider.java
@@ -35,10 +35,9 @@
import org.jkiss.utils.CommonUtils;
import javax.naming.Context;
+import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.InitialDirContext;
-import javax.naming.directory.SearchControls;
+import javax.naming.directory.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
@@ -72,44 +71,58 @@ public Map authExternalUser(
LdapSettings ldapSettings = new LdapSettings(providerConfig);
Hashtable environment = creteAuthEnvironment(ldapSettings);
- String fullUserDN = userName;
+ Map userData = null;
+ if (!isFullDN(userName) && CommonUtils.isNotEmpty(ldapSettings.getLoginAttribute())) {
+ userData = validateAndLoginUserAccessByUsername(userName, password, ldapSettings);
- if (!fullUserDN.startsWith(ldapSettings.getUserIdentifierAttr())) {
- fullUserDN = String.join("=", ldapSettings.getUserIdentifierAttr(), userName);
}
- if (CommonUtils.isNotEmpty(ldapSettings.getBaseDN()) && !fullUserDN.endsWith(ldapSettings.getBaseDN())) {
- fullUserDN = String.join(",", fullUserDN, ldapSettings.getBaseDN());
+ if (userData == null) {
+ String fullUserDN = buildFullUserDN(userName, ldapSettings);
+ validateUserAccess(fullUserDN, ldapSettings);
+ userData = authenticateLdap(fullUserDN, password, ldapSettings, null, environment);
}
+ return userData;
+ }
+ /**
+ * Find user and validate in ldap by uniq parameter from identityProviders
+ *
+ */
+ private Map validateAndLoginUserAccessByUsername(
+ @NotNull String login,
+ @NotNull String password,
+ @NotNull LdapSettings ldapSettings
+ ) throws DBException {
+ if (
+ CommonUtils.isEmpty(ldapSettings.getBindUserDN())
+ || CommonUtils.isEmpty(ldapSettings.getBindUserPassword())
+ ) {
+ return null;
+ }
+ Hashtable serviceUserContext = creteAuthEnvironment(ldapSettings);
+ serviceUserContext.put(Context.SECURITY_PRINCIPAL, ldapSettings.getBindUserDN());
+ serviceUserContext.put(Context.SECURITY_CREDENTIALS, ldapSettings.getBindUserPassword());
+ DirContext serviceContext;
- validateUserAccess(fullUserDN, ldapSettings);
-
- environment.put(Context.SECURITY_PRINCIPAL, fullUserDN);
- environment.put(Context.SECURITY_CREDENTIALS, password);
- DirContext context = null;
try {
- context = new InitialDirContext(environment);
- Map userData = new HashMap<>();
- userData.put(LdapConstants.CRED_USERNAME, findUserNameFromDN(fullUserDN, ldapSettings));
- userData.put(LdapConstants.CRED_SESSION_ID, UUID.randomUUID());
- return userData;
+ serviceContext = new InitialDirContext(serviceUserContext);
+ String userDN = findUserDN(serviceContext, ldapSettings, login);
+ if (userDN == null) {
+ return null;
+ }
+ return authenticateLdap(userDN, password, ldapSettings, login, creteAuthEnvironment(ldapSettings));
} catch (Exception e) {
throw new DBException("LDAP authentication failed: " + e.getMessage(), e);
- } finally {
- try {
- if (context != null) {
- context.close();
- }
- } catch (NamingException e) {
- log.warn("Error closing LDAP user context", e);
- }
}
}
+ /**
+ * Find user and validate in ldap by fullUserDN
+ */
private void validateUserAccess(@NotNull String fullUserDN, @NotNull LdapSettings ldapSettings) throws DBException {
if (
CommonUtils.isEmpty(ldapSettings.getFilter())
- || CommonUtils.isEmpty(ldapSettings.getBindUserDN())
- || CommonUtils.isEmpty(ldapSettings.getBindUserPassword())
+ || CommonUtils.isEmpty(ldapSettings.getBindUserDN())
+ || CommonUtils.isEmpty(ldapSettings.getBindUserPassword())
) {
return;
}
@@ -117,13 +130,10 @@ private void validateUserAccess(@NotNull String fullUserDN, @NotNull LdapSetting
var environment = creteAuthEnvironment(ldapSettings);
environment.put(Context.SECURITY_PRINCIPAL, ldapSettings.getBindUserDN());
environment.put(Context.SECURITY_CREDENTIALS, ldapSettings.getBindUserPassword());
- DirContext bindUserContext = null;
+ DirContext bindUserContext;
try {
bindUserContext = new InitialDirContext(environment);
-
- SearchControls searchControls = new SearchControls();
- searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
- searchControls.setTimeLimit(30_000);
+ SearchControls searchControls = createSearchControls();
var searchResult = bindUserContext.search(fullUserDN, ldapSettings.getFilter(), searchControls);
if (!searchResult.hasMore()) {
throw new DBException("Access denied");
@@ -132,14 +142,6 @@ private void validateUserAccess(@NotNull String fullUserDN, @NotNull LdapSetting
throw e;
} catch (Exception e) {
throw new DBException("LDAP user access validation by filter failed: " + e.getMessage(), e);
- } finally {
- if (bindUserContext != null) {
- try {
- bindUserContext.close();
- } catch (NamingException e) {
- log.warn("Error closing LDAP bind user context", e);
- }
- }
}
}
@@ -153,6 +155,53 @@ private static Hashtable creteAuthEnvironment(LdapSettings ldapS
return environment;
}
+ private String findUserDN(DirContext serviceContext, LdapSettings ldapSettings, String userIdentifier) throws DBException {
+ try {
+ String searchFilter = buildSearchFilter(ldapSettings, userIdentifier);
+ SearchControls searchControls = new SearchControls();
+ searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+ searchControls.setReturningAttributes(new String[]{"distinguishedName"});
+ String baseDN = getBaseDN(serviceContext, ldapSettings);
+
+ NamingEnumeration results = serviceContext.search(baseDN, searchFilter, searchControls);
+
+ if (results.hasMore()) {
+ return results.next().getNameInNamespace();
+ }
+ return null;
+ } catch (Exception e) {
+ throw new DBException("Error finding user DN: " + e.getMessage(), e);
+ }
+ }
+
+ private String getBaseDN(DirContext serviceContext, LdapSettings ldapSettings) throws DBException {
+ if (CommonUtils.isEmpty(ldapSettings.getBaseDN())) {
+ return getRootDN(serviceContext);
+ }
+ return ldapSettings.getBaseDN();
+ }
+
+ private String buildSearchFilter(LdapSettings ldapSettings, String userIdentifier) {
+ String userFilter = String.format("(%s=%s)", ldapSettings.getLoginAttribute(), userIdentifier);
+ if (CommonUtils.isNotEmpty(ldapSettings.getFilter())) {
+ return String.format("(&%s%s)", userFilter, ldapSettings.getFilter());
+ }
+ return userFilter;
+ }
+
+ private String getRootDN(DirContext adminContext) throws DBException {
+ try {
+ Attributes attributes = adminContext.getAttributes("", new String[]{"namingContexts"});
+ Attribute namingContexts = attributes.get("namingContexts");
+ if (namingContexts != null && namingContexts.size() > 0) {
+ return (String) namingContexts.get(0);
+ }
+ throw new DBException("Root DN not found in namingContexts");
+ } catch (Exception e) {
+ throw new DBException("Error retrieving root DN: " + e.getMessage(), e);
+ }
+ }
+
@NotNull
private String findUserNameFromDN(@NotNull String fullUserDN, @NotNull LdapSettings ldapSettings)
throws DBException {
@@ -180,7 +229,11 @@ public DBWUserIdentity getUserIdentity(
if (CommonUtils.isEmpty(userName)) {
throw new DBException("LDAP user name is empty");
}
- return new DBWUserIdentity(userName, userName);
+ String displayName = JSONUtils.getString(authParameters, LocalAuthProviderConstants.CRED_DISPLAY_NAME);
+ if (CommonUtils.isEmpty(displayName)) {
+ displayName = userName;
+ }
+ return new DBWUserIdentity(userName, displayName);
}
@Nullable
@@ -239,4 +292,61 @@ public void refreshSession(
public Object getInputUsername(@NotNull Map cred) {
return cred.get(LdapConstants.CRED_USERNAME);
}
+
+ private boolean isFullDN(String userName) {
+ return userName.contains(",") && userName.contains("=");
+ }
+
+ private String buildFullUserDN(String userName, LdapSettings ldapSettings) {
+ String fullUserDN = userName;
+
+ if (!fullUserDN.startsWith(ldapSettings.getUserIdentifierAttr())) {
+ fullUserDN = String.join("=", ldapSettings.getUserIdentifierAttr(), userName);
+ }
+ if (CommonUtils.isNotEmpty(ldapSettings.getBaseDN()) && !fullUserDN.endsWith(ldapSettings.getBaseDN())) {
+ fullUserDN = String.join(",", fullUserDN, ldapSettings.getBaseDN());
+ }
+
+ return fullUserDN;
+ }
+
+ private SearchControls createSearchControls() {
+ SearchControls searchControls = new SearchControls();
+ searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+ searchControls.setTimeLimit(30_000);
+ return searchControls;
+ }
+
+ private Map authenticateLdap(
+ String userDN,
+ String password,
+ LdapSettings ldapSettings,
+ @Nullable String login,
+ Hashtable environment
+ ) throws DBException {
+ environment.put(Context.SECURITY_PRINCIPAL, userDN);
+ environment.put(Context.SECURITY_CREDENTIALS, password);
+ DirContext userContext = null;
+ try {
+ userContext = new InitialDirContext(environment);
+ Map userData = new HashMap<>();
+ userData.put(LdapConstants.CRED_USERNAME, findUserNameFromDN(userDN, ldapSettings));
+ userData.put(LdapConstants.CRED_SESSION_ID, UUID.randomUUID());
+ if (login != null) {
+ userData.put(LdapConstants.CRED_DISPLAY_NAME, login);
+ }
+ return userData;
+ } catch (Exception e) {
+ throw new DBException("LDAP authentication failed: " + e.getMessage(), e);
+ } finally {
+ if (userContext != null) {
+ try {
+ userContext.close();
+ } catch (NamingException e) {
+ log.warn("Error closing LDAP user context", e);
+ }
+ }
+ }
+ }
+
}
diff --git a/server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapConstants.java b/server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapConstants.java
index dac3e989b1..8b18c1fb6e 100644
--- a/server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapConstants.java
+++ b/server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapConstants.java
@@ -24,9 +24,11 @@ public interface LdapConstants {
String PARAM_BIND_USER_PASSWORD = "ldap-bind-user-pwd";
String PARAM_FILTER = "ldap-filter";
String PARAM_USER_IDENTIFIER_ATTR = "ldap-identifier-attr";
+ String PARAM_LOGIN = "ldap-login";
String CRED_USERNAME = "user";
+ String CRED_DISPLAY_NAME = "displayName";
String CRED_USER_DN = "user-dn";
String CRED_PASSWORD = "password";
String CRED_SESSION_ID = "session-id";
diff --git a/server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapSettings.java b/server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapSettings.java
index 538e2df1b3..79310dadad 100644
--- a/server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapSettings.java
+++ b/server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapSettings.java
@@ -33,6 +33,7 @@ public class LdapSettings {
private final String bindUser;
private final String bindUserPassword;
private final String filter;
+ private final String loginAttribute;
protected LdapSettings(
@@ -48,6 +49,7 @@ protected LdapSettings(
this.bindUser = providerConfiguration.getParameterOrDefault(LdapConstants.PARAM_BIND_USER, "");
this.bindUserPassword = providerConfiguration.getParameterOrDefault(LdapConstants.PARAM_BIND_USER_PASSWORD, "");
this.filter = providerConfiguration.getParameterOrDefault(LdapConstants.PARAM_FILTER, "");
+ this.loginAttribute = providerConfiguration.getParameterOrDefault(LdapConstants.PARAM_LOGIN, "");;
}
@@ -85,4 +87,8 @@ public String getBindUserPassword() {
public String getFilter() {
return filter;
}
+
+ public String getLoginAttribute() {
+ return loginAttribute;
+ }
}