/scala-ldap

Primary LanguageScalaApache License 2.0Apache-2.0

Scala-LDAP: an LDAP client library for Scala

This project is part of Rudder - configuration management made easy.

See: http://rudder-project.org for more information.

Important notice to users looking for a generic scala-ldap solution

Rudder uses LDAP as its main storage for configuraiton policies. So we developped Scala-LDAP to interact more naturally with that back-end. That project used UnboundID LDAP SDK (https://www.unboundid.com/products/ldap-sdk/) under the hound, because we believe it’s the best tool out there for the job.

Even if we tried to build a generic Scala/LDAP SDK, it is today quite heavy linked to Rudder use cases and the Rudder project. Moreover, it has a dependency to liftweb because of its Box type, which may have been an okay-ish idea circa 2009, but not a so good one today.

Finally, we don’t have a maven repository with the Scala-LDAP dependencies available, so you are going to need to build them in order to use Scala-LDAP (see the build chapter below)

Authors

License

This project is licensed under ASF 2.0 license, see the provided "LICENSE" file or http://www.apache.org/licenses/LICENSE-2.0.html

Contributing

Thank you for your interest in your our project! The contribution process is detailed here: http://www.rudder-project.org/HowToContribute

Synopsis

This package is a Scala wrapper around UnboundID LDAP SDK. Its goal is to provide an efficient and idiomatic Scala library to deal with LDAP directories. The efficient part is accomplished by the great UnboundID SDK, and so we will focus on the idiomatic Scala construction, especially:

  • providing something like the loan pattern for LDAP connection handling;

  • providing a monad API for connection (and so for operation composition);

  • mainly using immutable data structures (return collection, attributes, etc);

  • using Option (or Lift Box) in place of semantic null return;

  • using other idiomatic Scala goodies: conversion, apply, meaningful operators (but also providing full string method name), case classes, etc

  • trying to keep the same hierarchy / logic as UnboundID.

Note: the first attempt for this module was to provide a blind Scala LDAP API implementation. Today, it seems that for the coming years, the only stable, autonomous and efficient LDAP SDK will remain UnboundID’s one. The OpenDS/ApacheDS project is still young, and there are no other known alternatives. So, we choose to not try too hard to hide UnboundID, but still provide nice import scheme.

Build

How to build Scala-LDAP ?

# clone and build dependencies for scala-ldap
export RUDDER_VERSION=x.y.z #ex: 2.11.7, 3.2.1, etc
# some directory used has base path for scala-ldap sources and dependencies
export $BASE=/tmp/some_directory
# checkout and build dependencies for scala-ldap
cd $BASE
git clone https://github.com/Normation/rudder-parent-pom.git && cd rudder-parent-pom
git checkout   -b rudder_$RUDDER_VERSION $RUDDER_VERSION
mvn install
cd $BASE
git clone https://github.com/Normation/rudder-commons.git && cd rudder-commons
git checkout   -b rudder_$RUDDER_VERSION $RUDDER_VERSION
mvn install
# now actually clone and build scala-ldap
cd $BASE
git clone https://github.com/Normation/scala-ldap.git && cd scala-ldap
git checkout   -b rudder_$RUDDER_VERSION $RUDDER_VERSION
mvn install

Now, you should have a scala-ldap jar in your repository and start using it.

Usage

The logical use of scala-ldap is in a for / yield loop, like:

    for {
      con           <- ldapConnectionProvider
      searchResults <- con.search(...)
      updatedLdif   <- con.save(...)
    } yield {
      updatedLdif
    }

Where ldapConnectionProvider is an instance of https://github.com/Normation/scala-ldap/blob/master/src/main/scala/com/normation/ldap/sdk/LDAPConnectionProvider.scala, typically configured thanks to IoC from a java-land point of view or something similar. You have several flavor of connection providers, read only ones (RO…​) or read/write ones (RW…​), anonymous or not, pooled or not.

So for retrieving user properties, you will do the search/searchOne on the wanted branch, something like:

--------------------------------------
   case class User(cn: String, sn: String, email: String)
val users = new DN("ou=Users, o=My Company")
for {
  con         <- ldapConnectionProvider
  userEntries <- con.searchOne(users, ALL, "cn", "sn", "email")
} yield {
  //here, you have an seq of LDAPEntries, access properties, map to business object, etc
  userEntries.flatMap( e => for {
     cn    <- e("cn")
     sn    <- e("sn")
     email <- e("email)
   } yield {
     User(cn, sn, email)
   }
}
For the bind part, we didn't have that on the wrapper (our use of LDAP is quite unconventionnal), so you will have to use the direct unboundid connection object for that:

    ldapConnectionProvider.map { con =>
            val bindResult = con.backed.bind("name", "password") //or if you need something more complexe, use a BindRequest object
    }


==== Example from Rudder

Here is an example of a standard pattern of interacting with that library:
https://github.com/Normation/rudder/blob/2.9.2/rudder-core/src/main/scala/com/normation/rudder/repository/ldap/LDAPRuleRepository.scala#L76

With some comments:

def get(id:RuleId) : Box[Rule] = { for { //get the connection to actually do operation con ← ldap ?~! "Error with the LDAP connection" //that message will appened to the actual connection problem

// retrieve the LDAP entry by its DN
entry <- con.get(rudderDit.RULES.configRuleDN(id.value))
   //map the rule entry to the rule business object
   rule  <- mapper.entry2Rule(crEntry) ?~! "Error when transforming LDAP entry into a rule for id %s. Entry: %s".format(id, crEntry)
  } yield {
    //return the rule object
    rule
  }
}