LDAP Auth for Bonita BPM Community

1
-2
-1

I'm looking for LDAP authentication for the Bonita BPM version 6.5 community (non-subscription) edition. Has anyone been able to do this and can steer me to something that will work?

3 answers

1
+4
-1
This one is the BEST answer!

I was able to get BonitaSoft community edition to talk to our college's Active Directory server. The code isn't very clean and I am a VERY much a newbie at java coding. However, here's what I did:

  1. I complied the 6.3.8 version using the instructions mentioned in this thread.

  2. I couldn't successfully compile all the 6.3.8 code for some reason. The portal stuff failed. But the auth code is in the bonita-engine section. Since I was able to get that far, I proceeded to add some LDAP code. I added it to AuthenticationServiceImpl.java, located in the BonitaBPM-build-6-3-8/bonita-engine/services/bonita-authentication/bonita-authentication-api-impl/src/main/java/org/bonitasoft/engine/authentication/impl directory of the source code. The reason I did not write my own java class was because A) I'm a newbie and wanted to write as little as possible, and B) I wanted the ldap code to "fall through" to the standard authentication, in case someone was logging in with an admin, technical, or portal admin account. PLEASE NOTE that this setup requires that the end users in your Bonita organization have usernames that exactly match the SAMAccountname in Active Directory.

  3. The easiest-to-use ldap api for java I could find was the Apache Directory LDAP API. So I first wrote my code in Eclipse to ensure that I could talk to my Active Directory server. Since there isn't an easy "bind as user" function in this api, I first bind as a bind user, then search for the end-users's name as passed into AuthenticationServiceImpl. The search returns a complete DN for that user, and I bind using that complete DN and the password passed into AuthenticationServiceImpl.

  4. I modified the pom.xml located in BonitaBPM-build-6-3-8/bonita-engine/services/bonita-authentication/bonita-authentication-api-impl to include a dependency for the apache ldap API:

<dependency>
  <groupId>org.apache.directory.api</groupId>
  <artifactId>api-all</artifactId>
  <version>1.0.0-M30</version>
  </dependency>
  1. I re-compiled the stuff in bonita-engine until I got everything to work right. I then dropped the new .jar file found in BonitaBPM-build-6-3-8/bonita-engine/bpm/bonita-server/target/ into /opt/BonitaBPMCommunity-6.3.8-Tomcat-6.0.37/webapps/bonita/WEB-INF/lib/, along with all the apache ldap .jar files.

  2. Here is AuthenticationServiceImpl, as modified by me:

/**
 * Copyright (C) 2011-2012, 2014 BonitaSoft S.A.
 * BonitaSoft, 32 rue Gustave Eiffel - 38000 Grenoble
 * This library is free software; you can redistribute it and/or modify it under the terms
 * of the GNU Lesser General Public License as published by the Free Software Foundation
 * version 2.1 of the License.
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License along with this
 * program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301, USA.
 **/

package org.bonitasoft.engine.authentication.impl;

import java.io.Serializable;
import java.util.Map;

import org.bonitasoft.engine.authentication.AuthenticationConstants;
import org.bonitasoft.engine.authentication.GenericAuthenticationService;
import org.bonitasoft.engine.commons.LogUtil;
import org.bonitasoft.engine.identity.IdentityService;
import org.bonitasoft.engine.identity.SUserNotFoundException;
import org.bonitasoft.engine.identity.model.SUser;
import org.bonitasoft.engine.log.technical.TechnicalLogSeverity;
import org.bonitasoft.engine.log.technical.TechnicalLoggerService;
/**
 * Apache LDAP-related imports:
 */

import java.io.IOException;
import org.apache.directory.api.ldap.model.cursor.CursorException;
import org.apache.directory.api.ldap.model.cursor.SearchCursor;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.message.Response;
import org.apache.directory.api.ldap.model.message.SearchRequest;
import org.apache.directory.api.ldap.model.message.SearchRequestImpl;
import org.apache.directory.api.ldap.model.message.SearchResultEntry;
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.ldap.client.api.LdapConnection;
import org.apache.directory.ldap.client.api.LdapNetworkConnection;

/**
 * @author Elias Ricken de Medeiros
 * @author Matthieu Chaffotte
 * @author Hongwen Zang
 * @author Julien Reboul
 * @author Celine Souchet
 */

public class AuthenticationServiceImpl implements GenericAuthenticationService {

    private final IdentityService identityService;

    private final TechnicalLoggerService logger;
   
    private final String lDAPServer = "put.your.hostname.here";
    private final int lDAPPort = 389;
    private final String bindString = "CN=put,OU=your,OU=full,OU=bind,DC=user,DC=dn,DC=here";
    private final String bindPassword =  "binduserpasswordgoeshere";
    private final String baseDN = "dc=enduser, dc=base, dc=dn";
    private String userBindDN = "";

    public AuthenticationServiceImpl(final IdentityService identityService, final TechnicalLoggerService logger) {
        this.identityService = identityService;
        this.logger = logger;
    }

    /**
     * @see org.bonitasoft.engine.authentication.GenericAuthenticationService#checkUserCredentials(java.util.Map)
     */

    @Override
    public String checkUserCredentials(Map<String, Serializable> credentials) {
        final String methodName = "checkUserCredentials";
        try {
              final String password = String.valueOf(credentials.get(AuthenticationConstants.BASIC_PASSWORD));
              final String userName = String.valueOf(credentials.get(AuthenticationConstants.BASIC_USERNAME));
              final SUser user = identityService.getUserByUserName(userName);
              if (logger.isLoggable(this.getClass(), TechnicalLogSeverity.TRACE)) {
                logger.log(this.getClass(), TechnicalLogSeverity.TRACE, LogUtil.getLogBeforeMethod(this.getClass(), methodName));
              }
            /**
            * Ldap code down to the END OF LDAP SECTION is  
            * Copyright (C) 2015 Snow College
            * 150 E. College Ave., Ephraim, UT
            * This ldap code is free software; you can redistribute it and/or modify it under the terms
            * of the GNU Lesser General Public License as published by the Free Software Foundation
            * version 2.1 of the License.
            * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
            * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
            * See the GNU Lesser General Public License for more details.
            * You should have received a copy of the GNU Lesser General Public License along with this
            * program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
            * Floor, Boston, MA 02110-1301, USA.
            */
 
            /*
             * Check via ldap first.  If it can't be contacted, or it fails, check via static database
            */


              LdapConnection connection = new LdapNetworkConnection(lDAPServer, lDAPPort);
              try {
                connection.bind(bindString, bindPassword);
                SearchRequest req = new SearchRequestImpl();
                req.setScope( SearchScope.SUBTREE );
                req.addAttributes( "dn" );
                req.setTimeLimit( 0 );
                req.setBase( new Dn( baseDN ) );
                req.setFilter( "(samAccountName=" + userName + ")");
 
                SearchCursor searchCursor = connection.search(req);
                try {
                  searchCursor.next();
                } catch (CursorException c){
                  System.err.println("failed to search the LDAP cursor for username: " + userName);
                }
               
                  // looks like we found the username.  Try binding with the provided username and password.
                  try {
                    Response response = searchCursor.get();
                    // process the SearchResultEntry
                    if ( response instanceof SearchResultEntry )
                    {
                        org.apache.directory.api.ldap.model.entry.Entry resultEntry = ( ( SearchResultEntry ) response ).getEntry();
                        userBindDN = resultEntry.toString();
                        userBindDN = userBindDN.replace("Entry\n","");
                        userBindDN = userBindDN.replace("dn:","");
                        userBindDN = userBindDN.trim();
                      LdapConnection userConnection = new LdapNetworkConnection( lDAPServer, lDAPPort );
                      try {
                       
                        System.err.println("connecting as: "+ userBindDN);
                        userConnection.bind(userBindDN, password);
                          System.err.println("connection succeeded as " + userBindDN);
                      } catch (LdapException e2) {
                        System.err.println("end user failed to connect as " + userBindDN);
                      }
                      try {
                        userConnection.close();
                      } catch (IOException e) {
                        System.err.println("unable to close LDAP user connection userConnection for userName: " + userName);
                      }
                      return userName;
                    }
                  } catch (CursorException c) {
                    System.err.println("failed to get a searchCursor for userName: "+userName);
                  }
 
                } catch (LdapException e) {
                System.err.println("failed to bind as the bind user");
              }
          /**
           * END OF LDAP SECTION.  Even if an authentication wasn't successful,
           * we still need to try to connect as a non-LDAP (i.e., admin) user:
           */

         
          if (identityService.chechCredentials(user, password)) {
            if (logger.isLoggable(this.getClass(), TechnicalLogSeverity.TRACE)) {
                logger.log(this.getClass(), TechnicalLogSeverity.TRACE, LogUtil.getLogAfterMethod(this.getClass(), methodName));
            }
            return userName;
        }
        if (logger.isLoggable(this.getClass(), TechnicalLogSeverity.TRACE)) {
            logger.log(this.getClass(), TechnicalLogSeverity.TRACE, LogUtil.getLogAfterMethod(this.getClass(), methodName));
        }
    }  catch (final SUserNotFoundException sunfe) {
            if (logger.isLoggable(this.getClass(), TechnicalLogSeverity.TRACE)) {
                logger.log(this.getClass(), TechnicalLogSeverity.TRACE, LogUtil.getLogOnExceptionMethod(this.getClass(), methodName, sunfe));
            }
    }
    return null;
  }
}  
  1. The code could be improved by making it refer to a .conf file to get the ldap host, the ldap bind user name and password, bind dn, end user dn suffix, etc.

Comments

Submitted by Sean McP on Thu, 05/28/2015 - 05:31

All I can say is WOW, what a wonderful piece of community spirit.

Thank you Phil, for trying it, for getting it working, and finally, for publishing it.

I like you am not a JAVA programmer but have learnt along the way... :)

In the same spirit I offer the following (Groovy) code that could be modified to easily refer to a properties file for the configuration etc.

In it I also use a include from jasypt.org for encrypted properties file. Please see their website for more details.

The config file must be placed in directory "/webapps/myCompany/properties/" and would be formatted as follows:

ldap.lDAPServer=put.your.hostname.here
ldap.lDAPPort=389
ldap.bindString=CN=put,OU=your,OU=full,OU=bind,DC=user,DC=dn,DC=here
ldap.bindPassword=binduserpasswordgoeshere
ldap.baseDN=dc=enduser, dc=base, dc=dn
ldap.userBindDN=

an encrypted version of the above file could look like:

ldap.lDAPServer=ENC(sknva;uhga;kjgbs41fg)
ldap.lDAPPort=ENC(fghfsdbf7575)
ldap.bindString=ENC(7686fkrhfgis7fgsdhg7htlsghlsrs5ths8yghgliitg78hslghhgl578thsl)
ldap.bindPassword=ENC(zkbfkvuygr7gha7tyafbhdflbas78tyaufh2983208rz.uziluhgzr7gh)
ldap.baseDN=ENC(zjhbvzkvbz7vl;a84th;afubvzlbhtg84h;t;s;uvhbz;rg)
ldap.userBindDN=ENC(sdfdfs)

The Groovy properties code, which is really a subroutine I use to pull text from language files (if you add this to Development->Manage Groovy Scripts you can use it is your scripts to get get from multiple properties files using String myString = myGroovyText.getProperty("myTextKey");), **must **be modified to work in the ldap code above:

/**
  * Copyright (C) 2015 Gubernare Ltd.,
  * London, United Kingdom
  *
  * This code is free software; you can redistribute it and/or modify it under the terms
  * of the GNU Lesser General Public License as published by the Free Software Foundation
  * version 2.1 of the License.
  *
  * This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  * See the GNU Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public License along with this
  * program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
  * Floor, Boston, MA 02110-1301, USA.
  */


         import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
         import org.jasypt.properties.EncryptableProperties;
         
         import java.util.regex.Pattern;
         
         import java.io.File;
         import java.io.FileInputStream;
         import java.util.HashMap;
         import java.util.Map;
         import java.util.Properties;
         import java.util.logging.Logger;
         
         def static getPropertyString(String key){
                 
         Logger logger= Logger.getLogger("org.bonitasoft");
         int d = 0;
         boolean debug = false; //change to true for logging
         
         //preample code - preparation
         //get catalina home - can sometimes NOT be set so work it through...
         def thisModule = " getPropertyString: ";
         String catalinaHome = System.getProperty("CATALINA_HOME");
         String catalinaPath = System.getProperty("CATALINA_PATH");
         if(debug){d++; logger.severe(d+thisModule+": CATALINA_HOME: "+catalinaHome);}
         if(debug){d++; logger.severe(d+thisModule+": CATALINA_PATH: "+catalinaPath);}
                 
         if(debug){d++; logger.severe(d+thisModule+": if (catalinaHome == null && catalinaPath == null){");}
         if (catalinaHome == null && catalinaPath == null){
                 
                 if(debug){d++; logger.severe(d+thisModule+": CATASTOPHIC ERROR CATALINA Not found: ");}

                 String strClassPath = System.getProperty("java.class.path");
                 if(debug){d++; logger.severe(d+thisModule+": strClassPath: "+strClassPath);}
                 //20150201 new version to get to webapps - START
                 if (strClassPath.indexOf(";") != 0 ){

                         //strClassPath is concatenated and must be reduced to one dir
                         if(debug){d++; logger.severe(d+thisModule+"strClassPath - is concatenated?");}
                       
                         String[] catHome = strClassPath.split(Pattern.quote(";"));
                         for (String singleCatHome : catHome){
                                 
                                 if(debug){d++; logger.severe(d+thisModule+"singleCatHome : "+singleCatHome);}
                                 if (singleCatHome.indexOf("bin") != 0){
                                         String noBin = singleCatHome.substring(0, singleCatHome.indexOf("bin"));
                                         
                                         File f = new File(noBin+"webapps");
                                         if(debug){d++; logger.severe(d+thisModule+"noBin (webapps): "+noBin+"webapps");}
                                         if (f.exists() && f.isDirectory()) {
                                                 if(debug){d++; logger.severe(d+thisModule+"singleCatHome Found webapps : "+noBin+"webapps");}
                                                 catalinaHome = noBin;
                                                 break;
                                         }
                                }
                        }
                 }
                 else{
                         //strClassPath is not concated and is OK?
                 }
                 //20150201 new version to get to webapps - END
                 
         }
         else if (catalinaHome == null && catalinaPath != null){
                 if(debug){d++; logger.severe(d+thisModule+"Set Home = Path");}
                 catalinaHome = catalinaPath;
         }
         else { // we have a CatalinaHome - but is it concatenated?
                 if(debug){d++; logger.severe(d+thisModule+"CatalinaHome - but is it concatenated?");}
                 
                 if (catalinaHome.indexOf(";") != 0 ){
                         //catalinaHome is concatenated and must be reduced to one dir
                         if(debug){d++; logger.severe(d+thisModule+"CatalinaHome - is concatenated?");}
                       
                         String[] catHome = catalinaHome.split(Pattern.quote(";"));
                         for (String singleCatHome : catHome){
                                 if(debug){d++; logger.severe(d+thisModule+"CatalinaHome : "+singleCatHome);}
                                 if (singleCatHome.indexOf("webapps") != 0){
                                        if(debug){d++; logger.severe(d+thisModule+"CatalinaHome Found webapps : "+singleCatHome);}
                                        catalinaHome = singleCatHome;
                                        break;
                                }
                        }
                 }
                 else{
                         //catalinaHome is not concated and is OK
                 }
         }

         //set encryption
         StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
         encryptor.setPassword("myPassword");
         Properties propsEnc = new EncryptableProperties(encryptor);
         
         //set locale - defaults
         String defaultLang = "en";
         String defCountry = "US";
         
         Locale locale = Locale.getDefault();
         String lang = locale.getLanguage();
         String country = locale.getCountry();
         
     String propertiesDir = "/webapps/myCompany/properties/";
     String fileNameEncrypted = "fileNameEncrypted_";
     String fileNameNotEncrypted = "fileNameNotEncrypted_";
         
         String propertiesFileEncLocale = propertiesDir+fileNameEncrypted+lang+"_"+country+".properties";
         String propertiesFileEncDefLoc = propertiesDir+fileNameEncrypted+defaultLang+"_"+defCountry+".properties";
         String propertiesFileEnc = propertiesDir+fileNameEncrypted+".properties";
         String propertiesFileLocale = propertiesDir+fileNameNotEncrypted+lang+"_"+country+".properties";
         String propertiesFileDefLoc = propertiesDir+fileNameNotEncrypted+defaultLang+"_"+defCountry+".properties";
         String propertiesFile = propertiesDir+fileNameNotEncrypted+".properties";
         
         
         try{ //1
                 try{ //encrypted locale
                         if(debug){d++; logger.severe(d+thisModule+": try: "+catalinaHome + propertiesFileEncLocale);}
                         propsEnc.load(new FileInputStream(new File(catalinaHome + propertiesFileEncLocale)));
                 }
                 catch(Exception e0){
                         try{ //encrypted default
                                 if(debug){d++; logger.severe(d+thisModule+": try: "+catalinaHome + propertiesFileEncDefLoc);}
                                 propsEnc.load(new FileInputStream(new File(catalinaHome + propertiesFileEncDefLoc)));
                         }
                         catch(Exception e1){
                                 try{ //encrypted
                                         if(debug){d++; logger.severe(d+thisModule+": try: "+catalinaHome + propertiesFileEnc);}
                                         propsEnc.load(new FileInputStream(new File(catalinaHome + propertiesFileEnc)));
                                 }
                                 catch(Exception e2){
                                         try{ // locale
                                                 if(debug){d++; logger.severe(d+thisModule+": try: "+catalinaHome + propertiesFileLocale);}
                                                 propsEnc.load(new FileInputStream(new File(catalinaHome + propertiesFileLocale)));
                                         }
                                         catch(Exception e3){
                                                 try{ // locale
                                                         if(debug){d++; logger.severe(d+thisModule+": try: "+catalinaHome + propertiesFileDefLoc);}
                                                         propsEnc.load(new FileInputStream(new File(catalinaHome + propertiesFileDefLoc)));
                                                 }
                                                 catch(Exception e4){
                                                         try{ // default
                                                                 if(debug){d++; logger.severe(d+thisModule+": try: "+catalinaHome + propertiesFile);}
                                                                 propsEnc.load(new FileInputStream(new File(catalinaHome + propertiesFile)));
                                                         }
                                                         catch(Exception e5){
                                                                 logger.severe(thisModule+": fileNameNotEncrypted.properties Error (E5-0): File Not Found: " + e5.toString());
                                                                 logger.severe(thisModule+": fileNameNotEncrypted.properties Error (E5-1): File Not Found: " + +catalinaHome + propertiesFile.toString());
                                                                 return "Error (e5): fileNameNotEncrypted.properties Error (E5): File Not Found";
                                                         }
                                                 }
                                         }
                                 }
                         }
                 }
         
                 try{
                         if(debug){d++; logger.severe(d+thisModule+": Key: "+key);}
                         return propsEnc.getProperty(key);
                 }
                 catch(Exception ex3){
                         logger.severe(thisModule+": Error (ex6): Message Not Found: "+key);
                         return "Error (ex6): Message Not Found: "+key;
                 }
         
         }
         catch(Exception ex0){
                 logger.severe(thisModule+": Fatal Error (EX0): File Not Found: " + ex0.toString());
                 return ": Fatal Error (EX0): File Not Found: ";
         }

Hope it helps, regards

Seán

Submitted by phil.allred on Thu, 05/28/2015 - 06:57

Thanks Seán. I'll look at it and see what I can do. It may take me a bit of time to re-post a version that uses a config file -- I'm still a slow java coder :)

Submitted by yasseroemi on Thu, 09/07/2017 - 13:11

Hi,

It would be nice if you could post your project to Github

Regards

1
0
-1

the very good developper

1
0
-1

Have a look at

http://community.bonitasoft.com/answers/ldap-bonita-65#node-24721

Which will guide you to

http://ironman.darthgibus.net/?p=57 (for 5.x but is an indicator)

regards

Comments

Submitted by phil.allred on Thu, 05/14/2015 - 21:55

Thanks, Sean. I'm going to try to build something. I think that your instructions at: http://documentation.bonitasoft.com/building-bonita-bpm-source-files-0 for version 6.5 may be need a few corrections:

  1. There are lots of git libraries that that appear to have moved away from 1.0.x to 6.1.x

  2. The pom.xml's bonita-integration-tests/bonita-test-utils/bonita-server-test-utils/pom.xml and bonita-integration-tests/pom.xml make reference to jdbc connectors for microsoft and oracle. However, you must download and install them into a local repository, and then make reference to the local repository as follows, in order to get bonita-engine to build:

       <groupId>com.oracle</groupId>
                                    <artifactId>ojdb6</artifactId>
                                    <version>11.2.0</version>
                                    <scope>system</scope>
                            <systemPath>/....your user path.../.m2/repository/com/oracle/ojdbc6/11.2.0/ojdbc6-11.2.0.jar</systemPath>
                            </dependency>
                            <dependency>
                                    <groupId>com.microsoft.jdbc</groupId>
                                    <artifactId>sqlserver</artifactId>
                                    <version>4.0.2206.100</version>
                                    <scope>system</scope>
                                    <systemPath>/...your user path.../.m2/repository/com/microsoft/jdbc/sqlserver/4.0.2206.100/sqlserver-4.0.2206.100.jar</systemPath>
                            </dependency>

  3. The checkout statement for google-calendar-V3 has a typo. It should read: bonita-connector-google-calendar-v3-1.0.0

I'm a novice at java, but I will continue to post info as I proceed.

Submitted by jordan2 on Fri, 05/15/2015 - 14:35

Hi, I plan to connect my LDAP to Bonita as well and I'm going to start coding my own connector. Does it work the same for this bpmn sofware version (6.5.2) of are there specific implementation details for each release?

Submitted by phil.allred on Fri, 05/15/2015 - 16:20

Jordan, I don't know for sure -- I'm going to attempt to write one for 6.5.2. Someone from BonitaSoft would have to answer that. My guess is that they are same for all 6.x versions.

Notifications