Completed ESME-370: Refactor LDAP auth modules and related operations

git-svn-id: https://svn.apache.org/repos/asf/esme/trunk@1226313 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/server/src/main/scala/org/apache/esme/model/UserAuth.scala b/server/src/main/scala/org/apache/esme/model/UserAuth.scala
index 551d0b5..99f3776 100644
--- a/server/src/main/scala/org/apache/esme/model/UserAuth.scala
+++ b/server/src/main/scala/org/apache/esme/model/UserAuth.scala
@@ -49,8 +49,10 @@
 
 import scala.xml._
 
-import scalaz.{OptionW, Scalaz}
+import scalaz.{OptionW, Identity, IterV, Input, State, Scalaz}
+import scalaz.effects.{IO}
 import Scalaz._
+import IterV.{Empty => EmptyZ, Done => DoneZ, _}
 
 import org.apache.esme.actor._
 
@@ -326,12 +328,13 @@
     myLdapVendor
   }
 
+  @deprecated
   def getAttrs(dn : String) : Map[String, List[String]] = {
     var attrsMap = Map.empty[String, List[String]]
-    val attrs : Attributes = myLdap.attributesFromDn(dn)
-    if (attrs != null) {
-      val allAttrs = attrs.getAll();
-      if (allAttrs != null) {
+    val attributes = getLDAPAttributes(dn)
+    attributes.foreach(attrs => {
+      val allAttributes = Option(attrs.getAll());
+      allAttributes.foreach(allAttrs => {
         while(allAttrs.hasMore()) {
           val attribute = allAttrs.next().asInstanceOf[Attr];
           debug("Attribute name: '%s', has following values:".format(attribute.getID()))
@@ -342,9 +345,69 @@
           }
           attrsMap += (attribute.getID() -> attrValues)
         }
+      })
+    })
+    attrsMap
+  }
+
+  def getLDAPAttributes(dn: String): Option[Attributes] = {
+    //TODO: it's probably better to return Either or Validation
+    try {
+      // Ugly, but following operation
+      // throws NamingException in case
+      // given DN doesn't exist in LDAP
+      Option(myLdap.attributesFromDn(dn))
+    } catch {
+      case ne: javax.naming.NamingException => {
+        info("Naming Exception was thrown: %s".format(ne))
+        none
       }
     }
-    attrsMap
+  }
+
+  // Scalaz Iteratee was used
+  // returns pairs name->list of values
+  def getAttributesMap(dn: String) : Option[Map[String, List[String]]] = {
+    val attributes = getLDAPAttributes(dn)
+    attributes.flatMap(attrs => {
+      val allAttributes = Option(attrs.getAll());
+      allAttributes.map(allAttrs  => {
+        val res = enumNamingEnumeration(allAttrs, accum, get) map (_.run)
+        res.unsafePerformIO ~> Map.empty[String, List[String]]
+      })
+    })
+  }
+
+
+  def enumNamingEnumeration[E, A, T <: Attr](ne: NamingEnumeration[T], iter: IterV[E, A], constr: NamingEnumeration[T] => IO[E]): IO[IterV[E, A]] = {
+    def loop(i: IterV[E, A]): IO[IterV[E, A]] =
+      i.fold(done = (_, _) => i.pure[IO],
+        cont = k => next(ne) >>= (hasMore =>
+          if (!hasMore) i.pure[IO]
+          else constr(ne) >>= (t => loop(k(El(t))))))
+    loop(iter)
+  }
+
+  def next: NamingEnumeration[_ <: Attr] => IO[Boolean] = _.hasMore().pure[IO]
+
+  def get: NamingEnumeration[_ <: Attr] => IO[(String, List[String])] = ne => {
+    val attribute = ne.next().asInstanceOf[Attr];
+    debug("Attribute name: '%s', has following values:".format(attribute.getID()))
+    var attrValues = List.empty[String]
+    for(i <- 0 until attribute.size()) {
+      debug("Attribute value: '%s'".format(attribute.get(i)))
+      attrValues ::= attribute.get(i).toString
+    }
+    (attribute.getID() -> attrValues).pure[IO]
+  }
+
+  def accum: IterV[(String, List[String]), State[Map[String, List[String]], Unit]] = {
+    def step(s: State[Map[String, List[String]], Unit]): Input[(String, List[String])] => IterV[(String, List[String]), State[Map[String, List[String]], Unit]] = {
+      case El(x1, x2) => Cont(step(s.withs((_: Map[String, List[String]]) + (x1 -> x2))))
+      case EmptyZ() => Cont(step(s))
+      case EOF() => DoneZ(s, EOF[(String, List[String])])
+    }
+    Cont(step(state[Map[String, List[String]], Unit](s => (s, ()))))
   }
 
   def constructDistinguishedName(who : String, isGroup : Boolean = false) = {
@@ -391,48 +454,50 @@
             val httpRequest: HTTPRequest = req.request
             val hrs = httpRequest.asInstanceOf[HTTPRequestServlet]
             val hsr: HttpServletRequest = hrs.req
-            val username : String = hsr.getRemoteUser
+            val username = Option(hsr.getRemoteUser)
             debug("Username: '%s'".format(username))
-            if(username!=null){
-              val currentRoles = rolesToCheck.filter(hsr.isUserInRole(_))
-              debug("User from HTTP Request: %s has following roles=%s".format(username, currentRoles))
-              if(currentRoles.size == 0) {
-                debug("No roles have been found")
-                S.error(S.?("base_user_err_unknown_creds"))
-              } else {
-                currentRoles.map(cr => {
-                currentRole = cr
-                (for {
-                    user <- UserAuth.find(By(UserAuth.authKey, username),
-                                          By(UserAuth.authType, moduleName)).flatMap(_.user.obj) or
-                    User.find(By(User.nickname, username))
-                  } yield user) match {
-                    case Full(user) => {
-                      debug("User: '%s' has been found".format(user.niceName))
-                      logInUser(user)
-                    }
-                    case _ => {
-                      val usr = User.createAndPopulate.nickname(username).saveMe
-                      //find and save additional attributes in LDAP if it's enabled
-                      val ldapEnabled = Props.getBool("ldap.enabled") openOr false
-                      if(ldapEnabled) {
-                        val ldapAttrs = getAttrs(constructDistinguishedName(username))
-                        val firstName = ldapAttrs("givenName").head
-                        val lastName = ldapAttrs("sn").head
-                        val mail = ldapAttrs("mail").head
-                        debug("Attributes from LDAP for user '%s'. Firstname: '%s', lastname: '%s', email: '%s'".format(username, firstName, lastName, mail))
-                        usr.firstName(firstName).lastName(lastName).save
+            username.cata(uName => {
+              rolesToCheck.filter(hsr.isUserInRole(_)) match {
+                case Nil => {
+                  debug("No roles have been found")
+                  S.error(S.?("base_user_err_unknown_creds"))
+                }
+                case xs => {
+                  debug("User from HTTP Request: %s has following roles=%s".format(uName, xs))
+                  xs.map(cr => {
+                    currentRole = cr
+                    (for {
+                      user <- UserAuth.find(By(UserAuth.authKey, uName),
+                        By(UserAuth.authType, moduleName)).flatMap(_.user.obj) or
+                        User.find(By(User.nickname, uName))
+                    } yield user) match {
+                      case Full(user) => {
+                        debug("User: '%s' has been found".format(user.niceName))
+                        logInUser(user)
                       }
-                      UserAuth.create.authType(moduleName).user(usr).authKey(username).save
-                      logInUser(usr)
+                      case _ => {
+                        val usr = User.createAndPopulate.nickname(uName).saveMe
+                        //find and save additional attributes in LDAP if it's enabled
+                        val ldapEnabled = Props.getBool("ldap.enabled") openOr false
+                        if(ldapEnabled) {
+                          getAttributesMap(constructDistinguishedName(uName)).map(ldapAttrs => {
+                            ldapAttrs.get("givenName").flatMap(_.headOption.map(usr.firstName(_)))
+                            ldapAttrs.get("sn").flatMap(_.headOption.map(usr.lastName(_)))
+                            //TODO: There's no corresponding property in User's Mapper
+                            //ldapAttrs.get("mail").flatMap(_.headOption.map(usr))
+                            debug("Attributes from LDAP for user '%s'. Firstname: '%s', lastname: '%s'".format(uName, usr.firstName, usr.lastName))
+                            usr.save
+                          })
+                        }
+                        UserAuth.create.authType(moduleName).user(usr).authKey(uName).save
+                        logInUser(usr)
+                      }
                     }
-                  }
-                })
+                  })
+                }
               }
-            } else {
-              S.error(S.?("base_user_err_unknown_creds"))
-            }
-
+            },
+            S.error(S.?("base_user_err_unknown_creds")))
           }
           case Empty => {
             S.error(S.?("base_user_err_unknown_creds"))
@@ -468,7 +533,7 @@
       if(ldapEnabled) {
         val name = S.param("username").map(_.trim.toLowerCase) openOr ""
         val pwd = S.param("password").map(_.trim) openOr ""
-        if(myLdap.bindUser(constructNameWithPrefix(name), pwd) && checkRoles(constructDistinguishedName(name))) {
+        if(myLdap.bindUser(constructNameWithPrefix(name), pwd) && !checkRoles(constructDistinguishedName(name)).isEmpty) {
           (for {
               user <- UserAuth.find(By(UserAuth.authKey, name),
                                     By(UserAuth.authType, moduleName)).flatMap(_.user.obj) or
@@ -480,12 +545,14 @@
             case Empty =>
               val usr = User.createAndPopulate.nickname(name).saveMe
               //find and save additional attributes in LDAP if it's enabled
-              val ldapAttrs = getAttrs(constructDistinguishedName(name))
-              val firstName = ldapAttrs("givenName").head
-              val lastName = ldapAttrs("sn").head
-              val mail = ldapAttrs("mail").head
-              debug("Attributes from LDAP for user '%s'. Firstname: '%s', lastname: '%s', email: '%s'".format(name, firstName, lastName, mail))
-              usr.firstName(firstName).lastName(lastName).save
+              getAttributesMap(constructDistinguishedName(name)).map(ldapAttrs => {
+                ldapAttrs.get("givenName").flatMap(_.headOption.map(usr.firstName(_)))
+                ldapAttrs.get("sn").flatMap(_.headOption.map(usr.lastName(_)))
+                //TODO: There's no corresponding property in User's Mapper
+                //ldapAttrs.get("mail").flatMap(_.headOption.map(usr))
+                debug("Attributes from LDAP for user '%s'. Firstname: '%s', lastname: '%s'".format(name, usr.firstName, usr.lastName))
+                usr.save
+              })
               UserAuth.create.authType(moduleName).user(usr).authKey(name).save
               logInUser(usr)
           }
@@ -498,18 +565,23 @@
     }
   }
 
-  def checkRoles(who : String) : Boolean = {
-    for (role <-rolesToCheck) {
-      val ldapAttrs = getAttrs(constructDistinguishedName(role, true))
-      val uniqueMember = ldapAttrs("uniqueMember").head
-      debug("'uniqueMember' attribute value: '%s'".format(uniqueMember))
-      if(who == uniqueMember) {
-        currentRole = role
-        return true
-      }
+  def checkRoles(who : String) : Option[String] = {
+    var matchedRoles: List[Option[String]] = Nil
+    for (role <- rolesToCheck) {
+        matchedRoles ::= getAttributesMap(constructDistinguishedName(role, true)).flatMap(ldapAttrs => {
+          ldapAttrs.get("uniqueMember").flatMap(_.headOption.flatMap(uniqueMember => {
+            if(who ≟ uniqueMember) {
+              currentRole = role
+              some(role)
+            } else {
+              none
+            }
+          }))
+      })
     }
-    debug("No roles have been found")
-    return false;
+    // At the moment only one (first matched) role is taken into account
+    // therefore First Option Monoid was used
+    matchedRoles.map(_.fst).asMA.sum.value
   }
 
   def createHolder(): FieldSet = new FieldSet {