Scala and ldap

Playing around with Scala, a very nice jvm language!,
found Lift (web framework),
looks simple and funny !

I was looking for ldap authentication with lift,
i wasn’t able to find nothing :(.

So i started to play with it,
Here, I install an openldap server and some simple scala code to play with it.

  • Install openldap and some sample data (using arch linux)

    
    1- sudo pacman -S openldap
    2- slappasswd -h {MD5} -s password (generates ldap password to use it later in config and user ldif file)
       {MD5}X03MO1qnZdYdgyfeuILPmQ==
    
  • Generate the ldap structure in initial_structure.ldif file

    
    dn: dc=company,dc=com
    dc: company
    description: LDAP Main object
    objectClass: organization
    objectClass: dcObject
    o: company.com
    
    dn: ou=Users,dc=company,dc=com
    ou: Users
    objectClass: organizationalUnit
    
    dn: ou=Groups,dc=company,dc=com
    ou: Groups
    objectClass: top
    objectClass: organizationalUnit
    
    dn: cn=main_group,ou=Groups,dc=company,dc=com
    gidNumber: 2000
    objectClass: posixGroup
    objectClass: top
    cn: main_group
    
    dn: cn=secondary_group,ou=Groups,dc=company,dc=com
    gidNumber: 2001
    objectClass: posixGroup
    objectClass: top
    cn: secondary_group
    
  • Configure ldap server in /etc/openldap/slapd.conf

    
    include         /etc/openldap/schema/core.schema
    include         /etc/openldap/schema/cosine.schema
    include         /etc/openldap/schema/courier.schema
    include         /etc/openldap/schema/inetorgperson.schema
    include         /etc/openldap/schema/nis.schema
    
    allow bind_v2
    password-hash {md5}
    
    pidfile   /var/run/slapd.pid
    argsfile  /var/run/slapd.args
    
    database        bdb
    suffix          "dc=company,dc=com"
    rootdn          "cn=admin,dc=company,dc=com"
    rootpw          {MD5}X03MO1qnZdYdgyfeuILPmQ==
    
    directory       /var/lib/openldap/openldap-data
    index   objectClass     eq
    index   uid     eq
    
  • Populate initial ldap structure

    
    sudo /usr/sbin/slapadd -l initial_structure.ldif
    
  • Populate user and group data, for example in file users.ldif

    
    dn: uid=sample_user_1,ou=Users,dc=company,dc=com
    objectClass: top
    objectClass: person
    objectClass: organizationalPerson
    objectClass: inetOrgPerson
    objectClass: posixAccount
    objectClass: shadowAccount
    uid: sample_user_1
    cn: Test User
    sn: User
    givenName: Test
    userPassword: {MD5}X03MO1qnZdYdgyfeuILPmQ==
    loginShell: /bin/bash
    uidNumber: 10000
    gidNumber: 2000
    homeDirectory: /home/users/test/
    
  • Starts ldap server and add users.ldif

    
    sudo /etc/rc.d/sldap start
    ldapadd -x -D "cn=admin,dc=company,dc=com" -f users.ldif -W
    
  • Test ldap server searching for sample_user_1 user
    
    ldapsearch -x -D "cn=admin,dc=company,dc=com" -b "dc=company,dc=com" "(&(uid=sample_user_1))" -W
    


  • And now scala code :)
    
    import java.io.FileInputStream
    import java.util.{Hashtable, Properties}
    
    import javax.naming.Context
    import javax.naming.directory.{BasicAttributes, SearchControls}
    import javax.naming.ldap.{LdapName, InitialLdapContext}
    
    import scala.collection.jcl.{MapWrapper}
    import scala.util.logging.{Logged, ConsoleLogger}
    
    implicit def convert(javaMap: Hashtable[String, String]) = {
        Map.empty ++ new MapWrapper[String, String]() {
            def underlying = javaMap
        }
    } 
    
    type StringMap = Map[String, String]
    
    val DEFAULT_URL = "localhost"
    val DEFAULT_BASE_DN = ""
    val DEFAULT_USER = ""
    val DEFAULT_PASSWORD = ""
    
    object SimpleLDAPSearch {
        lazy val ldap: LDAPSearch = {
            if (properties() == null) {
                val p = new Properties()
                p.load(new FileInputStream(propertiesFile()))
    
                // automatically calls convert(javaMap: Hashtable[String, String])
                properties = () => p.asInstanceOf[StringMap]
            }
    
            new LDAPSearch(properties()) with ConsoleLogger
        }
    
        var properties: () => StringMap = () => null
        var propertiesFile: () => String = {
            () => "DEFAULT_PROPERTIES_FILE.properties"
        }
    }
    
    class LDAPSearch(parameters: StringMap) extends Logged {
        lazy val initialContext = getInitialContext(parameters)
    
        def search(filter: String) : List[String] = {
            log("--> LDAPSearch.search: Searching for '%s'".format(filter))
    
            var list = List[String]()
    
            val ctx = initialContext
    
            if (!ctx.isEmpty) {
                val result = ctx.get.search(parameters.getOrElse("ldap.base", DEFAULT_BASE_DN),
                                            filter,
                                            getSearchControls())
    
                while(result.hasMore()) {
                    var r = result.next()
                    list = list ::: List(r.getName)
                }
            }
    
            return list
        }
    
        def bindUser(dn: String, password: String) : Boolean = {
            log("--> LDAPSearch.bindUser: Try to bind user '%s'".format(dn))
    
            var result = false
    
            try {
                var env = new Hashtable[String, String]()
                env.put(Context.PROVIDER_URL, parameters.getOrElse("ldap.url", DEFAULT_URL))
                env.put(Context.SECURITY_AUTHENTICATION, "simple")
                env.put(Context.SECURITY_PRINCIPAL, dn + "," + parameters.get("ldap.base"))
                env.put(Context.SECURITY_CREDENTIALS, password)
                env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory")
    
                var ctx = Some(new InitialLdapContext(env, null))
    
                result = !ctx.isEmpty
                ctx.get.close
            }
            catch {
                case e: Exception => println(e)
            }
    
            log("--> LDAPSearch.bindUser: Bind successfull ? %s".format(result))
    
            return result
        }
    
        private def getInitialContext(props: StringMap) : Option[InitialLdapContext] = {
    
            log("--> LDAPSearch.getInitialContext: Get initial context from '%s'".format(props.get("ldap.url")))
    
            var env = new Hashtable[String, String]()
            env.put(Context.PROVIDER_URL, props.getOrElse("ldap.url", DEFAULT_URL))
            env.put(Context.SECURITY_AUTHENTICATION, "simple")
            env.put(Context.SECURITY_PRINCIPAL, props.getOrElse("ldap.userName", DEFAULT_USER))
            env.put(Context.SECURITY_CREDENTIALS, props.getOrElse("ldap.password", DEFAULT_PASSWORD))
            env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory")
    
            return Some(new InitialLdapContext(env, null))
        }
    
        private def getSearchControls() : SearchControls = {
            val searchAttributes = new Array[String](1)
            searchAttributes(0) = "cn"
    
            val constraints = new SearchControls()
            constraints.setSearchScope(SearchControls.SUBTREE_SCOPE)
            constraints.setReturningAttributes(searchAttributes)
            return constraints
        }
    }
    
    // SimpleLDAPSearch.propertiesFile = () => "ldap.properties"
    
    SimpleLDAPSearch.properties = () => {
        Map("ldap.url" -> "ldap://localhost",
            "ldap.userName" -> "cn=admin,dc=company,dc=com",
            "ldap.password" -> "password",
            "ldap.base" -> "dc=company,dc=com")
    }
    
    val list1 = SimpleLDAPSearch.ldap.search("(uid=sample_user_1)")
    println(SimpleLDAPSearch.ldap.bindUser(list1(0), "password"))
    

    The code is not exactly perfect,
    but shows how simple can scala be.

Here’s my favourites lines of code :

  • Automatically convert types

    
    implicit def convert(javaMap: Hashtable[String, String]) = {
        Map.empty ++ new MapWrapper[String, String]() {
            def underlying = javaMap
        }
    }
    

    To automatically convert a java.util.Hashtable (java.util.Properties) into a scala Map,
    Example :

    
    val hashtable: java.util.Hastable[String, String] = new java.util.Hashtable[String, String]()
    hashtable.put("some_key", "some_value")
    
    val map = hashtable.asInstanceOf[Map[String, String]]
    
  • A var that contains a method that returns the ldap properties file

    
    object SimpleLDAPSearch {
        var propertiesFile: () => String = {
            () => "DEFAULT_PROPERTIES_FILE.properties"
        }
    ..
    // The SimpleLDAPSearch singleton propertiesFile method can be override in any moment
    SimpleLDAPSearch.propertiesFile = () => "/tmp/ldap.properties"
    
About these ads
    • bubuzzz
    • February 11th, 2012

    dude, you are awesome. This is exactly what i looking for. Thank you very much

    • marco
    • October 28th, 2013

    Thanks a lot, you saved me a heap of trouble by posting this.
    However, you might want to update your example a bit: As of scala 2.8, there is an implicit conversion for java.util.properties in scala.collection.JavaConversions . So your code becomes a little bit simpler. However, this changes your properties.get(“some.key”) into properties.get(“some.key”).get, because you will get an Option[String] as a result.

  1. March 29th, 2013

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: