From 72ce16b2e8d37d5d1b0de9ea2141a281a906f716 Mon Sep 17 00:00:00 2001 From: Alexander Skoblikov Date: Mon, 20 May 2024 18:05:09 +0300 Subject: [PATCH] CB-931 ldap identity provider (#2609) * CB-931 ldap identity provider * CB-931 add bruteforce protection to ldap --------- Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com> --- .../io.cloudbeaver.model/META-INF/MANIFEST.MF | 1 + .../provider/fa/AbstractSessionExternal.java | 79 +++++++++ .../META-INF/MANIFEST.MF | 14 ++ .../build.properties | 5 + .../plugin.xml | 31 ++++ .../io.cloudbeaver.service.ldap.auth/pom.xml | 16 ++ .../service/ldap/auth/LdapAuthProvider.java | 166 ++++++++++++++++++ .../service/ldap/auth/LdapConstants.java | 29 +++ .../service/ldap/auth/LdapSession.java | 41 +++++ .../service/ldap/auth/LdapSettings.java | 54 ++++++ server/bundles/pom.xml | 2 + .../io.cloudbeaver.server.feature/feature.xml | 2 + 12 files changed, 440 insertions(+) create mode 100644 server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/provider/fa/AbstractSessionExternal.java create mode 100644 server/bundles/io.cloudbeaver.service.ldap.auth/META-INF/MANIFEST.MF create mode 100644 server/bundles/io.cloudbeaver.service.ldap.auth/build.properties create mode 100644 server/bundles/io.cloudbeaver.service.ldap.auth/plugin.xml create mode 100644 server/bundles/io.cloudbeaver.service.ldap.auth/pom.xml create mode 100644 server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapAuthProvider.java create mode 100644 server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapConstants.java create mode 100644 server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapSession.java create mode 100644 server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapSettings.java diff --git a/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF index fa743ca1e8..b1ef7fd2b3 100644 --- a/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF +++ b/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF @@ -22,6 +22,7 @@ Require-Bundle: org.jkiss.dbeaver.data.gis;visibility:=reexport, Export-Package: io.cloudbeaver, io.cloudbeaver.auth, io.cloudbeaver.auth.provider, + io.cloudbeaver.auth.provider.fa, io.cloudbeaver.auth.provider.local, io.cloudbeaver.auth.provisioning, io.cloudbeaver.websocket, diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/provider/fa/AbstractSessionExternal.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/provider/fa/AbstractSessionExternal.java new file mode 100644 index 0000000000..f0ac26016a --- /dev/null +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/auth/provider/fa/AbstractSessionExternal.java @@ -0,0 +1,79 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2024 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.auth.provider.fa; + +import org.jkiss.code.NotNull; +import org.jkiss.code.Nullable; +import org.jkiss.dbeaver.model.auth.*; + +import java.time.LocalDateTime; +import java.util.Map; + +public abstract class AbstractSessionExternal implements SMSessionExternal { + + @NotNull + protected final Map authParameters; + @NotNull + protected final SMSession parentSession; + @NotNull + protected final SMAuthSpace space; + + protected AbstractSessionExternal( + @NotNull SMSession parentSession, + @NotNull SMAuthSpace space, + @NotNull Map authParameters + ) { + this.parentSession = parentSession; + this.space = space; + this.authParameters = authParameters; + } + + @NotNull + @Override + public SMAuthSpace getSessionSpace() { + return space; + } + + @NotNull + @Override + public SMSessionContext getSessionContext() { + return this.parentSession.getSessionContext(); + } + + @Nullable + @Override + public SMSessionPrincipal getSessionPrincipal() { + return parentSession.getSessionPrincipal(); + } + + @NotNull + @Override + public LocalDateTime getSessionStart() { + return parentSession.getSessionStart(); + } + + @Override + public void close() { + // do nothing + } + + @Override + public Map getAuthParameters() { + return authParameters; + } +} diff --git a/server/bundles/io.cloudbeaver.service.ldap.auth/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.service.ldap.auth/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..983d13ae7d --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.ldap.auth/META-INF/MANIFEST.MF @@ -0,0 +1,14 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Vendor: Cloudbeaver LDAP +Bundle-Vendor: DBeaver Corp +Bundle-SymbolicName: io.cloudbeaver.service.ldap.auth;singleton:=true +Bundle-Version: 1.0.0.qualifier +Bundle-Release-Date: 20240506 +Bundle-RequiredExecutionEnvironment: JavaSE-17 +Bundle-ActivationPolicy: lazy +Bundle-ClassPath: . +Require-Bundle: org.jkiss.dbeaver.model;visibility:=reexport, + org.jkiss.dbeaver.registry;visibility:=reexport, + io.cloudbeaver.model +Automatic-Module-Name: io.cloudbeaver.service.ldap.auth diff --git a/server/bundles/io.cloudbeaver.service.ldap.auth/build.properties b/server/bundles/io.cloudbeaver.service.ldap.auth/build.properties new file mode 100644 index 0000000000..95692a59c9 --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.ldap.auth/build.properties @@ -0,0 +1,5 @@ +source.. = src/ +output.. = target/classes/ +bin.includes = .,\ + META-INF/,\ + plugin.xml diff --git a/server/bundles/io.cloudbeaver.service.ldap.auth/plugin.xml b/server/bundles/io.cloudbeaver.service.ldap.auth/plugin.xml new file mode 100644 index 0000000000..4abd568426 --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.ldap.auth/plugin.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/server/bundles/io.cloudbeaver.service.ldap.auth/pom.xml b/server/bundles/io.cloudbeaver.service.ldap.auth/pom.xml new file mode 100644 index 0000000000..09ebea89c1 --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.ldap.auth/pom.xml @@ -0,0 +1,16 @@ + + + 4.0.0 + + io.cloudbeaver + bundles + 1.0.0-SNAPSHOT + ../ + + io.cloudbeaver.service.ldap.auth + 1.0.0-SNAPSHOT + eclipse-plugin + + 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 new file mode 100644 index 0000000000..4a86e2fb4e --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapAuthProvider.java @@ -0,0 +1,166 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2024 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.service.ldap.auth; + +import io.cloudbeaver.DBWUserIdentity; +import io.cloudbeaver.auth.SMAuthProviderExternal; +import io.cloudbeaver.auth.SMBruteForceProtected; +import io.cloudbeaver.auth.provider.local.LocalAuthProviderConstants; +import io.cloudbeaver.model.session.WebSession; +import io.cloudbeaver.model.user.WebUser; +import org.jkiss.code.NotNull; +import org.jkiss.code.Nullable; +import org.jkiss.dbeaver.DBException; +import org.jkiss.dbeaver.model.DBPObject; +import org.jkiss.dbeaver.model.auth.SMSession; +import org.jkiss.dbeaver.model.data.json.JSONUtils; +import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; +import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration; +import org.jkiss.dbeaver.model.security.SMController; +import org.jkiss.utils.CommonUtils; + +import javax.naming.Context; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class LdapAuthProvider implements SMAuthProviderExternal, SMBruteForceProtected { + public LdapAuthProvider() { + } + + @Override + public Map authExternalUser( + @NotNull DBRProgressMonitor monitor, + @Nullable SMAuthProviderCustomConfiguration providerConfig, + @NotNull Map authParameters + ) throws DBException { + if (providerConfig == null) { + throw new DBException("LDAP provider config is null"); + } + String userName = JSONUtils.getString(authParameters, LdapConstants.CRED_USERNAME); + if (CommonUtils.isEmpty(userName)) { + throw new DBException("LDAP user name is empty"); + } + String password = JSONUtils.getString(authParameters, LdapConstants.CRED_PASSWORD); + if (CommonUtils.isEmpty(password)) { + throw new DBException("LDAP password is empty"); + } + String unit = CommonUtils.nullIfEmpty(JSONUtils.getString(authParameters, LdapConstants.CRED_UNITS)); + + LdapSettings ldapSettings = new LdapSettings(providerConfig); + Hashtable environment = new Hashtable<>(); + environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + + var ldapProviderUrl = "ldap://" + ldapSettings.getHost() + ":" + ldapSettings.getPort(); + environment.put(Context.PROVIDER_URL, ldapProviderUrl); + environment.put(Context.SECURITY_AUTHENTICATION, "simple"); + + String cn = "cn=" + userName; + var principal = Stream.of(cn, unit, ldapSettings.getBaseDN()) + .filter(CommonUtils::isNotEmpty) + .collect(Collectors.joining(",")); + + environment.put(Context.SECURITY_PRINCIPAL, principal); + environment.put(Context.SECURITY_CREDENTIALS, password); + try { + DirContext context = new InitialDirContext(environment); + context.close(); + Map userData = new HashMap<>(); + userData.put(LdapConstants.CRED_USERNAME, userName); + userData.put(LdapConstants.CRED_SESSION_ID, UUID.randomUUID()); + return userData; + } catch (Exception e) { + throw new DBException("LDAP authentication failed: " + e.getMessage(), e); + } + } + + @NotNull + @Override + public DBWUserIdentity getUserIdentity( + @NotNull DBRProgressMonitor monitor, + @Nullable SMAuthProviderCustomConfiguration customConfiguration, + @NotNull Map authParameters + ) throws DBException { + String userName = JSONUtils.getString(authParameters, LocalAuthProviderConstants.CRED_USER); + if (CommonUtils.isEmpty(userName)) { + throw new DBException("LDAP user name is empty"); + } + return new DBWUserIdentity(userName, userName); + } + + @Nullable + @Override + public DBPObject getUserDetails( + @NotNull DBRProgressMonitor monitor, + @NotNull WebSession webSession, + @NotNull SMSession session, + @NotNull WebUser user, + boolean selfIdentity + ) throws DBException { + return null; + } + + @NotNull + @Override + public String validateLocalAuth( + @NotNull DBRProgressMonitor monitor, + @NotNull SMController securityController, + @NotNull SMAuthProviderCustomConfiguration providerConfig, + @NotNull Map userCredentials, + @Nullable String activeUserId + ) throws DBException { + String userId = JSONUtils.getString(userCredentials, LdapConstants.CRED_USERNAME); + if (CommonUtils.isEmpty(userId)) { + throw new DBException("LDAP user id not found"); + } + return activeUserId == null ? userId : activeUserId; + } + + @Override + public SMSession openSession( + @NotNull DBRProgressMonitor monitor, + @NotNull SMSession mainSession, + @Nullable SMAuthProviderCustomConfiguration customConfiguration, + @NotNull Map userCredentials + ) throws DBException { + return new LdapSession(mainSession, mainSession.getSessionSpace(), userCredentials); + } + + @Override + public void closeSession(@NotNull SMSession mainSession, SMSession session) throws DBException { + + } + + @Override + public void refreshSession( + @NotNull DBRProgressMonitor monitor, + @NotNull SMSession mainSession, + SMSession session + ) throws DBException { + + } + + @Override + public Object getInputUsername(@NotNull Map cred) { + return cred.get(LdapConstants.CRED_USERNAME); + } +} 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 new file mode 100644 index 0000000000..02fcfbea6d --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapConstants.java @@ -0,0 +1,29 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2024 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.service.ldap.auth; + +public interface LdapConstants { + String PARAM_HOST = "ldap-host"; + String PARAM_PORT = "ldap-port"; + String PARAM_DN = "ldap-dn"; + + + String CRED_USERNAME = "user"; + String CRED_PASSWORD = "password"; + String CRED_UNITS = "units"; + String CRED_SESSION_ID = "session-id"; +} diff --git a/server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapSession.java b/server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapSession.java new file mode 100644 index 0000000000..c4326af395 --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapSession.java @@ -0,0 +1,41 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2024 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.service.ldap.auth; + +import io.cloudbeaver.auth.provider.fa.AbstractSessionExternal; +import org.jkiss.code.NotNull; +import org.jkiss.dbeaver.model.auth.SMAuthSpace; +import org.jkiss.dbeaver.model.auth.SMSession; +import org.jkiss.dbeaver.model.data.json.JSONUtils; + +import java.util.Map; + +public class LdapSession extends AbstractSessionExternal { + protected LdapSession( + @NotNull SMSession parentSession, + @NotNull SMAuthSpace space, + @NotNull Map authParameters + ) { + super(parentSession, space, authParameters); + } + + @NotNull + @Override + public String getSessionId() { + return JSONUtils.getString(authParameters, LdapConstants.CRED_SESSION_ID, "sessionNotFound"); + } +} 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 new file mode 100644 index 0000000000..9bce30ee14 --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.ldap.auth/src/io/cloudbeaver/service/ldap/auth/LdapSettings.java @@ -0,0 +1,54 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2024 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.service.ldap.auth; + +import org.jkiss.code.NotNull; +import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration; +import org.jkiss.utils.CommonUtils; + +public class LdapSettings { + @NotNull + private final SMAuthProviderCustomConfiguration providerConfiguration; + @NotNull + private final String host; + @NotNull + private final String baseDN; + private final int port; + + protected LdapSettings(SMAuthProviderCustomConfiguration providerConfiguration) { + this.providerConfiguration = providerConfiguration; + this.host = providerConfiguration.getParameter(LdapConstants.PARAM_HOST); + this.port = CommonUtils.isNotEmpty(providerConfiguration.getParameter(LdapConstants.PARAM_PORT)) ? Integer.parseInt( + providerConfiguration.getParameter(LdapConstants.PARAM_PORT)) : 389; + this.baseDN = providerConfiguration.getParameterOrDefault(LdapConstants.PARAM_DN, ""); + } + + + @NotNull + public String getBaseDN() { + return baseDN; + } + + @NotNull + public String getHost() { + return host; + } + + public int getPort() { + return port; + } +} diff --git a/server/bundles/pom.xml b/server/bundles/pom.xml index 5ce7dcb36f..4cd1b54263 100644 --- a/server/bundles/pom.xml +++ b/server/bundles/pom.xml @@ -24,7 +24,9 @@ io.cloudbeaver.service.rm io.cloudbeaver.service.rm.nio io.cloudbeaver.service.data.transfer + io.cloudbeaver.service.security + io.cloudbeaver.service.ldap.auth io.cloudbeaver.resources.drivers.base diff --git a/server/features/io.cloudbeaver.server.feature/feature.xml b/server/features/io.cloudbeaver.server.feature/feature.xml index 887f69b061..3b96765cfa 100644 --- a/server/features/io.cloudbeaver.server.feature/feature.xml +++ b/server/features/io.cloudbeaver.server.feature/feature.xml @@ -39,7 +39,9 @@ + +