Skip to content

Commit

Permalink
CB-931 ldap identity provider (#2609)
Browse files Browse the repository at this point in the history
* CB-931 ldap identity provider

* CB-931 add bruteforce protection to ldap

---------

Co-authored-by: Evgenia Bezborodova <[email protected]>
  • Loading branch information
alexander-skoblikov and EvgeniaBzzz authored May 20, 2024
1 parent 7cd22a8 commit 72ce16b
Show file tree
Hide file tree
Showing 12 changed files with 440 additions and 0 deletions.
1 change: 1 addition & 0 deletions server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, Object> authParameters;
@NotNull
protected final SMSession parentSession;
@NotNull
protected final SMAuthSpace space;

protected AbstractSessionExternal(
@NotNull SMSession parentSession,
@NotNull SMAuthSpace space,
@NotNull Map<String, Object> 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<String, Object> getAuthParameters() {
return authParameters;
}
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
source.. = src/
output.. = target/classes/
bin.includes = .,\
META-INF/,\
plugin.xml
31 changes: 31 additions & 0 deletions server/bundles/io.cloudbeaver.service.ldap.auth/plugin.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>

<plugin>
<extension point="org.jkiss.dbeaver.auth.provider">
<authProvider id="ldap" label="LDAP" description="LDAP authentication provider"
configurable="true"
class="io.cloudbeaver.service.ldap.auth.LdapAuthProvider"
icon="platform:/plugin/org.jkiss.dbeaver.model/icons/idp/ldap.png"
>
<configuration>
<propertyGroup label="LDAP" description="LDAP authentication">
<property id="ldap-host" label="Host" type="string" description="LDAP server host" required="true"/>
<property id="ldap-port" label="Port" type="integer" defaultValue="389" required="true" description="LDAP server port, default is 389"/>
<property id="ldap-dn" label="Base Distinguished Name" type="string"
description="Base Distinguished Name applicable for all users, example: dc=myOrg,dc=com"
required="true"
/>
</propertyGroup>
</configuration>
<credentials>
<propertyGroup label="Auth credentials">
<property id="units" label="Unit paths" type="string" description="LDAP unit paths separated by commas. Example ou=unit1,ou=unit2" user="true"/>
<property id="user" label="User name" type="string" description="LDAP user name" user="true"/>
<property id="password" label="User password" type="string" description="LDAP user password"
user="true" encryption="plain"/>
</propertyGroup>
</credentials>
</authProvider>
</extension>
</plugin>
16 changes: 16 additions & 0 deletions server/bundles/io.cloudbeaver.service.ldap.auth/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.cloudbeaver</groupId>
<artifactId>bundles</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.ldap.auth</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>

</project>
Original file line number Diff line number Diff line change
@@ -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<SMSession>, SMBruteForceProtected {
public LdapAuthProvider() {
}

@Override
public Map<String, Object> authExternalUser(
@NotNull DBRProgressMonitor monitor,
@Nullable SMAuthProviderCustomConfiguration providerConfig,
@NotNull Map<String, Object> 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<String, String> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> cred) {
return cred.get(LdapConstants.CRED_USERNAME);
}
}
Original file line number Diff line number Diff line change
@@ -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";
}
Original file line number Diff line number Diff line change
@@ -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<String, Object> authParameters
) {
super(parentSession, space, authParameters);
}

@NotNull
@Override
public String getSessionId() {
return JSONUtils.getString(authParameters, LdapConstants.CRED_SESSION_ID, "sessionNotFound");
}
}
Loading

0 comments on commit 72ce16b

Please sign in to comment.