Cb 6160 ldap user provisioning (#3354)

* CB-6160 ldap user provisioning

* CB-6160 ldap user provisioning

* CB-6160 ldap user provisioning

* CB-6160 ldap provisioning

* CB-6160-ldap-user-provisioning

* CB-6160 ldap user provisioning

* CB-6160 ldap user provisioning

* CB-6160 ldap user provisioning

* CB-6160 ldap user provisioning

* CB-6160 ldap user provisioning

* CB-6160 ldap user provisioning

* CB-6160 ldap user provisioning

* CB-6160 ldap user provisioning. Added copyrights

---------

Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
This commit is contained in:
Ruslan Musaev
2025-04-16 12:18:15 +02:00
committed by GitHub
co-authored by sergeyteleshev Evgenia
parent 8bce3f2357
commit 63f31d0a08
8 changed files with 184 additions and 77 deletions
@@ -22,8 +22,6 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPImage;
import org.jkiss.dbeaver.model.DBPNamedObjectLocalized;
import org.jkiss.dbeaver.model.DBPObjectWithDescriptionLocalized;
import org.jkiss.dbeaver.model.auth.AuthPropertyDescriptor;
import org.jkiss.dbeaver.model.auth.SMAuthProvider;
import org.jkiss.dbeaver.model.impl.AbstractDescriptor;
@@ -47,7 +45,7 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
private final IConfigurationElement cfg;
private final ObjectType implType;
private ObjectType implType;
private final Map<SMSubjectType, List<DBPPropertyDescriptor>> metaParameters = new HashMap<>();
private SMAuthProvider<?> instance;
private final DBPImage icon;
@@ -61,31 +59,31 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
private final boolean serviceProvider;
private final String[] requiredFeatures;
private final boolean isRequired;
private final String[] types;
private String[] types;
public WebAuthProviderDescriptor(IConfigurationElement cfg) {
super(cfg);
this.cfg = cfg;
this.implType = new ObjectType(cfg, "class");
this.icon = iconToImage(cfg.getAttribute("icon"));
this.configurable = CommonUtils.toBoolean(cfg.getAttribute("configurable"));
this.trusted = CommonUtils.toBoolean(cfg.getAttribute("trusted"));
this.isPrivate = CommonUtils.toBoolean(cfg.getAttribute("private"));
this.isRequired = CommonUtils.toBoolean(cfg.getAttribute("required"));
this.isAuthHidden = CommonUtils.toBoolean(cfg.getAttribute("authHidden"));
this.isCaseInsensitive = CommonUtils.toBoolean(cfg.getAttribute("caseInsensitive"));
this.serviceProvider = CommonUtils.toBoolean(cfg.getAttribute("serviceProvider"));
this.implType = new ObjectType(cfg, WebRegistryConstant.ATTR_CLASS);
this.icon = iconToImage(cfg.getAttribute(WebRegistryConstant.ATTR_ICON));
this.configurable = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_CONFIGURABLE));
this.trusted = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_TRUSTED));
this.isPrivate = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_PRIVATE));
this.isRequired = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_REQUIRED));
this.isAuthHidden = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_AUTH_HIDDEN));
this.isCaseInsensitive = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_CASE_INSENSITIVE));
this.serviceProvider = CommonUtils.toBoolean(cfg.getAttribute(WebRegistryConstant.ATTR_SERVICE_PROVIDER));
for (IConfigurationElement cfgElement : cfg.getChildren("configuration")) {
for (IConfigurationElement cfgElement : cfg.getChildren(WebRegistryConstant.TAG_CONFIGURATION)) {
List<WebAuthProviderProperty> properties = WebAuthProviderRegistry.readProperties(cfgElement, getId());
for (WebAuthProviderProperty property : properties) {
configurationParameters.put(CommonUtils.toString(property.getId()), property);
}
}
for (IConfigurationElement credElement : cfg.getChildren("credentials")) {
for (IConfigurationElement credElement : cfg.getChildren(WebRegistryConstant.TAG_CREDENTIALS)) {
credentialProfiles.add(new SMAuthCredentialsProfile(credElement));
}
for (IConfigurationElement mpElement : cfg.getChildren("metaParameters")) {
for (IConfigurationElement mpElement : cfg.getChildren(WebRegistryConstant.TAG_META_PARAMETERS)) {
SMSubjectType subjectType = CommonUtils.valueOf(SMSubjectType.class, mpElement.getAttribute("type"), SMSubjectType.user);
List<DBPPropertyDescriptor> metaProps = new ArrayList<>();
for (IConfigurationElement propGroup : ArrayUtils.safeArray(mpElement.getChildren(PropertyDescriptor.TAG_PROPERTY_GROUP))) {
@@ -94,24 +92,24 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
metaParameters.put(subjectType, metaProps);
}
String rfList = cfg.getAttribute("requiredFeatures");
String rfList = cfg.getAttribute(WebRegistryConstant.ATTR_REQUIRED_FEATURES);
requiredFeatures = CommonUtils.isEmpty(rfList) ? null : rfList.split(",");
String typesAttr = cfg.getAttribute("categories");
String typesAttr = cfg.getAttribute(WebRegistryConstant.ATTR_CATEGORIES);
this.types = CommonUtils.isEmpty(typesAttr) ? new String[0] : typesAttr.split(",");
}
@NotNull
public String getId() {
return cfg.getAttribute("id");
return cfg.getAttribute(WebRegistryConstant.ATTR_ID);
}
public String getLabel() {
return cfg.getAttribute("label");
return cfg.getAttribute(WebRegistryConstant.ATTR_LABEL);
}
public String getDescription() {
return cfg.getAttribute("description");
return cfg.getAttribute(WebRegistryConstant.ATTR_DESCRIPTION);
}
public DBPImage getIcon() {
@@ -214,6 +212,20 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
return types;
}
public void loadExtraConfig(IConfigurationElement ext) {
//todo read other props if it needs
String typesAttr = ext.getAttribute(WebRegistryConstant.ATTR_CATEGORIES);
this.types = CommonUtils.isEmpty(typesAttr) ? new String[0] : typesAttr.split(",");
this.implType = new ObjectType(ext, WebRegistryConstant.ATTR_CLASS);
for (IConfigurationElement cfgElement : ext.getChildren(WebRegistryConstant.TAG_CONFIGURATION)) {
List<WebAuthProviderProperty> properties = WebAuthProviderRegistry.readProperties(cfgElement, getId());
for (WebAuthProviderProperty property : properties) {
configurationParameters.put(CommonUtils.toString(property.getId()), property);
}
}
replaceContributor(ext.getContributor());
}
public boolean isServiceProvider() {
return serviceProvider;
}
@@ -59,8 +59,14 @@ public class WebAuthProviderRegistry {
for (IConfigurationElement ext : extConfigs) {
// Load webServices
if (TAG_AUTH_PROVIDER.equals(ext.getName())) {
WebAuthProviderDescriptor providerDescriptor = new WebAuthProviderDescriptor(ext);
this.authProviders.put(providerDescriptor.getId(), providerDescriptor);
WebAuthProviderDescriptor providerDescriptor;
WebAuthProviderDescriptor webAuthProviderDescriptor = authProviders.get(ext.getAttribute(WebRegistryConstant.ATTR_EXTENDED));
if (webAuthProviderDescriptor != null) {
webAuthProviderDescriptor.loadExtraConfig(ext);
} else {
providerDescriptor = new WebAuthProviderDescriptor(ext);
this.authProviders.put(providerDescriptor.getId(), providerDescriptor);
}
} else if (TAG_COMMON_PROVIDER_PROPERTIES.equals(ext.getName())) {
var commonProperties = new WebCommonAuthProviderPropertyDescriptor(ext);
this.commonProperties.add(commonProperties);
@@ -0,0 +1,39 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
*
* Licensed 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 io.cloudbeaver.registry;
public class WebRegistryConstant {
public static final String ATTR_ID = "id";
public static final String ATTR_EXTENDED = "extended";
public static final String ATTR_LABEL = "label";
public static final String ATTR_DESCRIPTION = "description";
public static final String ATTR_CLASS = "class";
public static final String ATTR_ICON = "icon";
public static final String ATTR_CONFIGURABLE = "configurable";
public static final String ATTR_TRUSTED = "trusted";
public static final String ATTR_PRIVATE = "private";
public static final String ATTR_REQUIRED = "required";
public static final String ATTR_AUTH_HIDDEN = "authHidden";
public static final String ATTR_CASE_INSENSITIVE = "caseInsensitive";
public static final String ATTR_REQUIRED_FEATURES = "requiredFeatures";
public static final String ATTR_CATEGORIES = "categories";
public static final String ATTR_SERVICE_PROVIDER = "serviceProvider";
public static final String TAG_CONFIGURATION = "configuration";
public static final String TAG_CREDENTIALS = "credentials";
public static final String TAG_META_PARAMETERS = "metaParameters";
}
@@ -12,4 +12,5 @@ Require-Bundle: org.jkiss.dbeaver.model;visibility:=reexport,
org.jkiss.dbeaver.registry;visibility:=reexport,
io.cloudbeaver.model
Bundle-Localization: OSGI-INF/l10n/bundle
Export-Package: io.cloudbeaver.service.ldap.auth
Automatic-Module-Name: io.cloudbeaver.service.ldap.auth
@@ -7,6 +7,7 @@
configurable="true"
class="io.cloudbeaver.service.ldap.auth.LdapAuthProvider"
icon="platform:/plugin/org.jkiss.dbeaver.model/icons/idp/ldap.png"
caseInsensitive="true"
>
<configuration>
<propertyGroup label="LDAP" description="LDAP authentication">
@@ -40,16 +40,11 @@ import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.UUID;
import java.util.*;
public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBruteForceProtected, SMAuthProviderAssigner {
private static final Log log = Log.getLog(LdapAuthProvider.class);
public static final String LDAP_AUTH_PROVIDER_ID = "ldap";
public static final String LDAP_ATTRIBUTE_OBJECT_GUID = "objectGUID";
public static final String LDAP_ATTRIBUTE_ENTRY_UUID = "entryUUID";
public LdapAuthProvider() {
}
@@ -89,6 +84,26 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
}
return userData;
}
@NotNull
@Override
public SMAutoAssign detectAutoAssignments(
@NotNull DBRProgressMonitor monitor,
@NotNull SMAuthProviderCustomConfiguration providerConfig,
@NotNull Map<String, Object> authParameters
) throws DBException {
List<String> autoAssignmentTeamIds = detectAutoAssignmentTeam(providerConfig, authParameters);
SMAutoAssign smAutoAssign = new SMAutoAssign();
autoAssignmentTeamIds.forEach(smAutoAssign::addExternalTeamId);
return smAutoAssign;
}
@Nullable
@Override
public String getExternalTeamIdMetadataFieldName() {
return LdapConstants.LDAP_META_GROUP_NAME;
}
/**
* Find user and validate in ldap by uniq parameter from identityProviders
*
@@ -155,13 +170,24 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
}
}
private String getAttributeValue(Attributes attributes, String attributeName) throws NamingException {
protected String getAttributeValue(Attributes attributes, String attributeName) throws NamingException {
Attribute attribute = attributes.get(attributeName);
return attribute != null ? attribute.get().toString() : null;
}
@NotNull
private static Hashtable<String, String> creteAuthEnvironment(LdapSettings ldapSettings) {
protected String getAttributeValueSafe(@NotNull Attributes attributes, @NotNull String attrName) {
try {
Attribute attr = attributes.get(attrName.toLowerCase());
return attr != null ? (String) attr.get() : "";
} catch (Exception e) {
log.debug("Can't extract '" + attrName + "' from ldap attributes");
return "";
}
}
@NotNull
public Hashtable<String, String> creteAuthEnvironment(LdapSettings ldapSettings) {
Hashtable<String, String> environment = new Hashtable<>();
environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
@@ -170,20 +196,37 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
return environment;
}
private String findUserDN(DirContext serviceContext, LdapSettings ldapSettings, String userIdentifier) throws DBException {
protected String findUserDN(DirContext serviceContext, LdapSettings ldapSettings, String userIdentifier) throws DBException {
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setReturningAttributes(new String[]{"distinguishedName"});
NamingEnumeration<SearchResult> results = findByFilter(
serviceContext,
ldapSettings,
buildSearchFilter(ldapSettings, userIdentifier),
searchControls
);
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<SearchResult> results = serviceContext.search(baseDN, searchFilter, searchControls);
if (results.hasMore()) {
return results.next().getNameInNamespace();
}
return null;
} catch (NamingException e) {
throw new DBException("Error finding user DN: " + e.getMessage(), e);
}
return null;
}
public NamingEnumeration<SearchResult> findByFilter(
@NotNull DirContext serviceContext,
@NotNull LdapSettings ldapSettings,
@NotNull String searchFilter,
@NotNull SearchControls searchControls
) throws DBException {
try {
String baseDN = getBaseDN(serviceContext, ldapSettings);
return serviceContext.search(baseDN, searchFilter, searchControls);
} catch (Exception e) {
throw new DBException("Error finding user DN: " + e.getMessage(), e);
}
@@ -338,7 +381,7 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setTimeLimit(30_000);
searchControls.setReturningAttributes(new String[]{LDAP_ATTRIBUTE_OBJECT_GUID, LDAP_ATTRIBUTE_ENTRY_UUID});
searchControls.setReturningAttributes(new String[]{"*", "+"});
return searchControls;
}
@@ -355,27 +398,30 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
DirContext userContext = null;
try {
userContext = new InitialDirContext(environment);
if (CommonUtils.isNotEmpty(login)) {
userData.put(LdapConstants.CRED_USERNAME, login);
userData.put(LdapConstants.CRED_USER_DN, userDN);
userData.put(LdapConstants.CRED_DISPLAY_NAME, findUserNameFromDN(userDN, ldapSettings));
} else {
SearchControls searchControls = createSearchControls();
String userId = "";
var searchResult = userContext.search(userDN, "objectClass=*", searchControls);
if (searchResult.hasMore()) {
SearchResult result = searchResult.next();
Attributes attributes = result.getAttributes();
userId = getAttributeValue(attributes, "objectGUID");
if (userId == null) {
userId = getAttributeValue(attributes, "entryUUID");
}
SearchControls searchControls = createSearchControls();
String userId = "";
var searchResult = userContext.search(userDN, "objectClass=*", searchControls);
if (searchResult.hasMore()) {
SearchResult result = searchResult.next();
Attributes attributes = result.getAttributes();
userId = getAttributeValue(attributes, "objectGUID");
if (userId == null) {
userId = getAttributeValue(attributes, "entryUUID");
}
userData.putIfAbsent(LdapConstants.CRED_USERNAME, CommonUtils.isNotEmpty(userId) ? userId : login);
userData.put(LdapConstants.CRED_USER_DN, userDN);
userData.put(LdapConstants.CRED_DISPLAY_NAME, findUserNameFromDN(userDN, ldapSettings));
userData.put(
LdapConstants.LDAP_META_GROUP_NAME,
getAttributeValueSafe(
attributes,
ldapSettings.getProviderConfiguration().getParameter(LdapConstants.LDAP_META_GROUP_NAME)
)
);
doCustomModifyUserDataAfterAuthentication(ldapSettings, attributes, userData);
}
userData.putIfAbsent(LdapConstants.CRED_USERNAME, CommonUtils.isNotEmpty(userId) ? userId : login);
userData.put(LdapConstants.CRED_USER_DN, userDN);
userData.put(LdapConstants.CRED_DISPLAY_NAME, findUserNameFromDN(userDN, ldapSettings));
userData.put(LdapConstants.CRED_SESSION_ID, UUID.randomUUID());
return userData;
} catch (Exception e) {
throw new DBException("LDAP authentication failed: " + e.getMessage(), e);
@@ -390,10 +436,11 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
}
}
protected void doCustomModifyUserDataAfterAuthentication(LdapSettings ldapSettings, Attributes attributes, Map<String, Object> userData) {
}
@NotNull
@Override
public SMAutoAssign detectAutoAssignments(
@NotNull DBRProgressMonitor monitor,
protected List<String> detectAutoAssignmentTeam(
@NotNull SMAuthProviderCustomConfiguration providerConfig,
@NotNull Map<String, Object> authParameters
) throws DBException {
@@ -411,18 +458,18 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
userDN = getUserDN(ldapSettings, JSONUtils.getString(authParameters, LdapConstants.CRED_DISPLAY_NAME));
}
if (userDN == null) {
return new SMAutoAssign();
return Collections.emptyList();
}
SMAutoAssign smAutoAssign = new SMAutoAssign();
smAutoAssign.addExternalTeamId(userDN);
List<String> result = new ArrayList<>();
result.add(userDN);
String groupDN = getGroupForMember(userDN, ldapSettings);
if (groupDN != null) {
smAutoAssign.addExternalTeamId(groupDN);
result.add(groupDN);
}
return smAutoAssign;
return result;
}
private String getUserDN(LdapSettings ldapSettings, String displayName) {
@@ -453,9 +500,4 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
}
return null;
}
@Override
public String getExternalTeamIdMetadataFieldName() {
return LdapConstants.LDAP_META_GROUP_NAME;
}
}
@@ -36,7 +36,7 @@ public class LdapSettings {
private final String loginAttribute;
protected LdapSettings(
public LdapSettings(
SMAuthProviderCustomConfiguration providerConfiguration
) {
this.providerConfiguration = providerConfiguration;
@@ -91,4 +91,9 @@ public class LdapSettings {
public String getLoginAttribute() {
return loginAttribute;
}
@NotNull
public SMAuthProviderCustomConfiguration getProviderConfiguration() {
return providerConfiguration;
}
}
@@ -193,7 +193,8 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
outer:
for (SMUserProvisioning user : userImportList.getUsers()) {
String authRole = user.getAuthRole() == null ? userImportList.getAuthRole() : user.getAuthRole();
for (String possibleUserId : List.of(user.getUserId(), user.getUserId().toLowerCase())) {
String userId = user.getUserId();
for (String possibleUserId : List.of(userId, userId.toLowerCase())) {
if (isSubjectExists(possibleUserId)) {
log.info("User already exist : " + possibleUserId);
setUserAuthRole(connection, possibleUserId, authRole);
@@ -201,7 +202,7 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
continue outer;
}
}
createUser(connection, user.getUserId().toLowerCase(), user.getMetaParameters(), true, authRole);
createUser(connection, userId.toLowerCase(), user.getMetaParameters(), true, authRole);
}
}
@@ -2522,7 +2523,7 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
createUser(
dbCon,
userId,
Map.of(),
(Map<String, String>) userCredentials.get(SMStandardMeta.KEY_META_PARAMS),
true,
resolveUserAuthRole(null, authRole)
);