Skip to content

Commit

Permalink
LDAP Authenticator creates Account if needed
Browse files Browse the repository at this point in the history
  • Loading branch information
msarti committed Mar 16, 2014
1 parent f7421ed commit 38508f2
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 29 deletions.
12 changes: 12 additions & 0 deletions modules/core/app/com/elogiclab/guardbee/core/Errors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ case class Errors(messages: Seq[Msg]) {

}

trait ErrorResults {

val AuthenticationError = Errors(Seq(Msg("guardbee.error.authentication_error")))
def InternalError(error_code: ErrorCodes.ErrorCode) = Errors(Seq(Msg("guardbee.error.internal", error_code)))
val AuthenticationFailedError = Errors(Seq(Msg("guardbee.error.authenticationFailed")))
}


object Errors {

val AuthenticationError = Errors(Seq(Msg("guardbee.error.authentication_error")))
Expand All @@ -68,6 +76,10 @@ object Errors {

}





object ErrorCodes extends Enumeration {
type ErrorCode = Value

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,22 @@ import com.elogiclab.guardbee.core.Msg
import org.joda.time.DateTime
import com.elogiclab.guardbee.core.UsernamePasswordAuthenticationToken
import com.elogiclab.guardbee.core.UsernamePasswordAuthenticator

import com.elogiclab.guardbee.core.UserService
import com.elogiclab.guardbee.core.GuardbeeService
import com.elogiclab.guardbee.core.ErrorResults

trait LdapUser extends User {
def userDN: String
}




/**
* @author Marco Sarti
*
*/
class LdapAuthenticatorPlugin(app: Application) extends LdapConfiguration(app) with UsernamePasswordAuthenticator with Plugin {
class LdapAuthenticatorPlugin(app: Application) extends LdapConfiguration(app) with UsernamePasswordAuthenticator with LdapErrorResults with Plugin {
val ProviderId = "ldap"

def connect: Either[Errors, LDAPConnection] = {
Expand All @@ -64,7 +69,7 @@ class LdapAuthenticatorPlugin(app: Application) extends LdapConfiguration(app) w
} catch {
case e: Throwable => {
logger.error("Error connecting to LDAP server: " + e.getMessage())
Left(Errors("guardbee.error.internal", ERROR_LDAP_UNAVAILABLE))
Left(InternalError(ERROR_LDAP_UNAVAILABLE))
}
}
}
Expand Down Expand Up @@ -100,24 +105,24 @@ class LdapAuthenticatorPlugin(app: Application) extends LdapConfiguration(app) w
val userDN = entry.getDN()
})
}
case (None,_) => {
case (None, _) => {
logger.error("Error creating user for " + userid + "; missing attribute " + entry.getAttribute(LDAPUsernameAttr))
Left(Errors("guardbee.error.ldap.missingAttribute", LDAPUsernameAttr))
Left(LdapMissingAttributeError(LDAPUsernameAttr))
}
case (_,None) => {
case (_, None) => {
logger.error("Error creating user for " + userid + "; missing attribute " + entry.getAttribute(LDAPemailAttr))
Left(Errors("guardbee.error.ldap.missingAttribute", LDAPemailAttr))
Left(LdapMissingAttributeError(LDAPemailAttr))
}

}
}
case _ => Left(Errors("guardbee.error.authenticationFailed"))
case _ => Left(AuthenticationFailedError)
}

} catch {
case e: Throwable => {
logger.error("Error retrieving user " + userid + ": " + e.getMessage())
Left(Errors("guardbee.error.authenticationFailed"))
Left(AuthenticationFailedError)
}
}
}
Expand All @@ -130,34 +135,59 @@ class LdapAuthenticatorPlugin(app: Application) extends LdapConfiguration(app) w
} catch {
case e: Throwable => {
logger.error("User " + userDN + " authentication failed: " + e.getMessage())
Left(Errors("guardbee.error.authenticationFailed"))
Left(AuthenticationFailedError)
}
}
}


def createAuthentication(u: LdapUser, remember_me:Option[Boolean]): Either[Errors, Authentication] = {
Right(Authentication(u.username, ProviderId, None, DateTime.now, remember_me.getOrElse(false)))
}

def createAccount(user: LdapUser) = {
GuardbeeService.UserService[User].getByEmail(user.email).map { u =>
Left(LdapEmailAlreadyInUseError)
}.getOrElse {
GuardbeeService.UserService[User].createUser(user, DefaultProfileRoles)
}
}

def authenticate(authToken: UsernamePasswordAuthenticationToken): Either[Errors, Authentication] = {
logger.debug(authToken.username + " attempts LDAP authentication")

def validateAccount(user: LdapUser) = {

GuardbeeService.UserService[User].getByUsername(user.username).map { u =>
u.isValid match {
case true => Right(u)
case false => Left(LdapInvalidAccountError)
}
}.getOrElse {
LDAPRequiresExistingAccount match {
case true => Left(LdapAccountNotFoundError)
case false => createAccount(user)
}
}

}


def createAuthentication(u: User, remember_me: Option[Boolean]): Either[Errors, Authentication] = {

Right(Authentication(u.username, ProviderId, None, DateTime.now, remember_me.getOrElse(false)))
}

def authenticate(authToken: UsernamePasswordAuthenticationToken): Either[Errors, Authentication] = {
logger.debug(authToken.username + " attempts LDAP authentication")

val result = connect.fold({ errors =>
Left(errors)
}, { connection =>
logger.debug("Connected to LDAP")
val auth = for(
user <- findDN(connection, authToken.username).right;
result <- bindUser(connection, user.userDN, authToken.password).right;
authentication <- createAuthentication(user, authToken.remember_me).right
val auth = for (
user <- findDN(connection, authToken.username).right;
result <- bindUser(connection, user.userDN, authToken.password).right;
validUser <- validateAccount(user).right;
authentication <- createAuthentication(validUser, authToken.remember_me).right
) yield authentication
connection.close
auth
})

result
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,15 @@ package com.elogiclab.guardbee.providers.ldap

import play.api.Play
import play.api.Application
import com.elogiclab.guardbee.core.Configuration

/**
* @author Marco Sarti
*
*/
class LdapConfiguration(application: Application) {
class LdapConfiguration(application: Application) extends Configuration(application) {


val applicationKey = "guardbee"

//LDAP
lazy val LDAPHost = application.configuration.getString(applicationKey + ".ldap.host").getOrElse("localhost")
lazy val LDAPPort = application.configuration.getInt(applicationKey + ".ldap.port").getOrElse(389)
Expand All @@ -46,7 +45,7 @@ class LdapConfiguration(application: Application) {
lazy val LDAPemailAttr = application.configuration.getString(applicationKey + ".ldap.emailAttr").getOrElse("mail")
lazy val LDAPUsernameAttr = application.configuration.getString(applicationKey + ".ldap.usernameAttr").getOrElse("uid")
lazy val LDAPSearchBase = application.configuration.getString(applicationKey + ".ldap.searchBase").getOrElse("dc=elogiclab,dc=com")

lazy val LDAPRequiresExistingAccount = application.configuration.getBoolean(applicationKey + ".ldap.requiresExistingAccount").getOrElse(false)

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Copyright (c) 2014 Marco Sarti <marco.sarti at gmail.com>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.elogiclab.guardbee.providers.ldap

import com.elogiclab.guardbee.core.ErrorResults
import com.elogiclab.guardbee.core.Errors
import com.elogiclab.guardbee.core.Msg

/**
* @author Marco Sarti
*
*/
trait LdapErrorResults extends ErrorResults {

def LdapMissingAttributeError(attr: String) = Errors(Seq(Msg("guardbee.error.ldap.missingAttribute", attr)))

val LdapInvalidAccountError = Errors(Seq(Msg("guardbee.error.ldap.invalidAccount")))

val LdapAccountNotFoundError = Errors(Seq(Msg("guardbee.error.ldap.accountNotFound")))

val LdapEmailAlreadyInUseError = Errors(Seq(Msg("guardbee.error.ldap.emailAlreadyInUse")))

}
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,28 @@ object LdapAuthenticatorSpec extends Specification {
}


"Should authenticate" in new WithApplication {
val plugin = new LdapAuthenticatorPlugin(app)
"Should authenticate" in new WithApplication(app = new FakeApplication(additionalPlugins = Seq("com.elogiclab.guardbee.core.GuardbeeServicePlugin"))) {
val plugin = new LdapAuthenticatorPlugin(app) {
override def validateAccount(user: LdapUser) = Right(user)
}
val result = plugin.authenticate(UsernamePasswordAuthenticationToken("msarti", "123456", None))

result must beRight

}
"Should NOT authenticate with bad credentials" in new WithApplication {
"Should NOT authenticate if account is invalid" in new WithApplication(app = new FakeApplication(additionalPlugins = Seq("com.elogiclab.guardbee.core.GuardbeeServicePlugin"))) {
val plugin = new LdapAuthenticatorPlugin(app) {
override def validateAccount(user: LdapUser) = Left(LdapInvalidAccountError)
}
val result = plugin.authenticate(UsernamePasswordAuthenticationToken("msarti", "123456", None))

result must beLeft

}



"Should NOT authenticate with bad credentials" in new WithApplication(app = new FakeApplication(additionalPlugins = Seq("com.elogiclab.guardbee.core.GuardbeeServicePlugin"))) {
val plugin = new LdapAuthenticatorPlugin(app)
val result = plugin.authenticate(UsernamePasswordAuthenticationToken("msarti", "bad", None))

Expand Down

0 comments on commit 38508f2

Please sign in to comment.