| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.apache.esme.model |
| |
| import net.liftweb._ |
| import common.Box._ |
| import common.Logger._ |
| import mapper._ |
| import openid._ |
| import util._ |
| import common._ |
| import Helpers._ |
| import http._ |
| |
| import net.liftweb._ |
| import http._ |
| import js._ |
| import js.jquery._ |
| import http.jquery._ |
| import JqJsCmds._ |
| import JsCmds._ |
| import SHtml._ |
| import JE._ |
| |
| import net.liftweb.openid._ |
| |
| import net.liftweb.ldap._ |
| |
| import provider.HTTPRequest |
| import provider.servlet.HTTPRequestServlet |
| |
| |
| import scala.xml._ |
| |
| import org.apache.esme.actor._ |
| |
| import org.openid4java.discovery.Identifier |
| import org.openid4java.consumer._ |
| import org.openid4java.util._ |
| |
| import javax.servlet.http.HttpServletRequest |
| |
| import _root_.javax.naming.NamingEnumeration |
| import _root_.javax.naming.directory.{Attributes, Attribute => Attr} |
| |
| |
| /** |
| * A table that keeps track of authtentications (username/pwd, openid, etc.) for |
| * a given user |
| */ |
| class UserAuth extends LongKeyedMapper[UserAuth] with IdPK { |
| def getSingleton = UserAuth |
| |
| object user extends MappedLongForeignKey(this, User) |
| |
| /** |
| * The module that's used for authentication |
| */ |
| object authType extends MappedString(this, 64) |
| |
| /** |
| * The key for authentication on the given type (e.g., email address, open-id, etc. |
| */ |
| object authKey extends MappedString(this, 2048) |
| |
| /** |
| * Data that's specific to the authentication module (e.g., a hashed password |
| */ |
| object authData extends MappedText(this) |
| } |
| |
| object UserAuth extends UserAuth with LongKeyedMetaMapper[UserAuth] with Logger { |
| override def dbIndexes = Index(authType, authKey) :: super.dbIndexes |
| |
| private var modules: Map[String, AuthModule] = Map() |
| |
| def register(module: AuthModule) { |
| modules += (module.moduleName -> module) |
| if (module.isDefault && defAuth.isEmpty) defAuth = Full(module) |
| module.performInit() |
| } |
| |
| def loginPresentation: List[NodeSeq] = |
| modules.values.toList.flatMap(_.loginPresentation) |
| |
| private var defAuth: Box[AuthModule] = Empty |
| |
| def defaultAuthModule: AuthModule = defAuth.open_! |
| } |
| |
| trait AuthModule extends Logger { |
| def loginPresentation: Box[NodeSeq] |
| def signupPresentation: Box[NodeSeq] = Empty |
| def moduleName: String |
| def performInit(): Unit |
| def isDefault = false |
| def createHolder(): FieldSet |
| } |
| |
| trait FieldSet { |
| def toForm: NodeSeq |
| def validate: List[FieldError] |
| def save(user: User): Unit |
| } |
| |
| object UserPwdAuthModule extends AuthModule { |
| def loginPresentation: Box[NodeSeq] = { |
| val ldapBind : CssBindFunc = "#ldapEnabled [value]" #> (Props.getBool("ldap.enabled") openOr false) |
| TemplateFinder.findAnyTemplate("templates-hidden" :: "upw_login_form" :: Nil) match { |
| case Full(tpl) => Full(ldapBind(tpl)) |
| case _ => Empty |
| } |
| } |
| |
| def moduleName: String = "upw" |
| |
| def performInit(): Unit = { |
| LiftRules.dispatch.append { |
| case Req("authentication" :: "login" :: Nil, _, PostRequest) => |
| val from = S.referer openOr "/" |
| |
| (for { |
| name <- S.param("username").map(_.trim.toLowerCase) |
| pwd <- S.param("password").map(_.trim) |
| user <- UserAuth.find(By(UserAuth.authKey, name), |
| By(UserAuth.authType, moduleName)).flatMap(_.user.obj) or |
| User.find(By(User.nickname, name)) |
| |
| userAuth <- UserAuth.find(By(UserAuth.user, user), By(UserAuth.authType, moduleName)) |
| if authenticatePassword(userAuth.authData.is, pwd) |
| } yield user) match { |
| case Full(user) => |
| User.logUserIn(user) |
| S.notice(S.?("base_user_msg_welcome", user.niceName)) |
| |
| case _ => |
| S.error(S.?("base_user_err_unknown_creds")) |
| } |
| |
| S.redirectTo(from) |
| } |
| } |
| |
| private def authenticatePassword(hashed: String, pwd: String): Boolean = |
| tryo { |
| val (salt :: hashedPw :: _) = hashed.roboSplit(";") |
| md5(salt+pwd) == hashedPw |
| } openOr false |
| |
| override def isDefault = true |
| |
| def createHolder(): FieldSet = new FieldSet { |
| private var pwd1 = "" |
| private var pwd2 = "" |
| private var email = "" |
| |
| def toForm: NodeSeq = |
| TemplateFinder.findAnyTemplate("templates-hidden" :: "upw_signup_form" :: Nil).map( |
| xhtml => |
| bind("signup", xhtml, |
| "email" -%> SHtml.text(email, s => email = s.trim.toLowerCase), |
| "pwd1" -%> SHtml.password(pwd1, s => pwd1 = s.trim), |
| "pwd2" -%> SHtml.password(pwd2, s => pwd2 = s.trim)) |
| ) openOr NodeSeq.Empty |
| |
| def validate: List[FieldError] = ( |
| if (MappedEmail.validEmailAddr_?(email)) Nil else { |
| val msg = S.?("base_user_err_bad_email") |
| S.error(msg) |
| List(FieldError(new FieldIdentifier { |
| override def uniqueFieldId: Box[String] = Full("email") |
| }, Text(msg))) |
| } |
| ) ::: ( |
| if (pwd1 != pwd2) { |
| val msg = S.?("base_user_err_mismatch_password") |
| DisplayMessage("messages", <b>{msg}</b>, 3 seconds, 3 seconds); |
| S.error(msg) |
| List(FieldError(new FieldIdentifier { |
| override def uniqueFieldId: Box[String] = Full("pwd1") |
| }, Text(msg))) |
| } else if (pwd1.length < 6) { |
| val msg = S.?("base_user_err_password_too_short") |
| S.error(msg) |
| List(FieldError(new FieldIdentifier { |
| override def uniqueFieldId: Box[String] = Full("pwd1") |
| }, Text(msg))) |
| } else Nil) |
| |
| def save(user: User): Unit = { |
| val salt = randomString(10) |
| val md5 = Helpers.md5(salt + pwd1) |
| UserAuth.create.user(user).authType(moduleName).authKey(email).authData(salt+";"+md5).save |
| } |
| } |
| } |
| |
| object OpenIDAuthModule extends AuthModule { |
| def loginPresentation: Box[NodeSeq] = |
| TemplateFinder.findAnyTemplate("templates-hidden" :: "openid_login_form" :: Nil) |
| |
| def moduleName: String = "openid" |
| |
| def performInit(): Unit = { |
| LiftRules.dispatch.append { |
| case Req("open_id" :: "login" :: Nil, _, PostRequest) => |
| val from = S.referer openOr "/" |
| |
| def logUserIn(openid: Box[Identifier], fo: Box[VerificationResult], exp: Box[Exception]): LiftResponse = { |
| (openid, exp) match { |
| case (Full(OpenIDAuthModule(user)), _) => |
| User.logUserIn(user) |
| S.notice(S.?("base_user_msg_welcome",user.niceName)) |
| |
| RedirectResponse(from, S responseCookies :_*) |
| |
| case (Full(id), _) => |
| S.error(S.?("base_user_err_openid_not_reg",id.getIdentifier())) |
| RedirectResponse(from, S responseCookies :_*) |
| |
| |
| case (_, Full(exp)) => |
| S.error(S.?("base_error_exception", exp.getMessage)) |
| RedirectResponse(from, S responseCookies :_*) |
| |
| case _ => |
| S.error(S.?("base_user_err_login", fo.map(_.getStatusMsg))) |
| RedirectResponse(from, S responseCookies :_*) |
| } |
| |
| |
| } |
| |
| for { |
| username <- S.param("open_id") |
| } ESMEOpenIDVendor.loginAndRedirect(username, logUserIn) |
| |
| |
| S.redirectTo(from) |
| } |
| } |
| |
| def createHolder(): FieldSet = new FieldSet { |
| def toForm: NodeSeq = NodeSeq.Empty |
| def validate: List[FieldError] = Nil |
| def save(user: User): Unit = {} |
| } |
| |
| def findOrCreate(openId: String): User = |
| UserAuth.find(By(UserAuth.authType, moduleName), By(UserAuth.authKey, openId)).flatMap(_.user.obj) match { |
| case Full(user) => user |
| case _ => val user = User.createAndPopulate.nickname(openId).saveMe |
| UserAuth.create.authType(moduleName).user(user).authKey(openId).save |
| User.logUserIn(user) |
| user |
| } |
| |
| def unapply(openId: Identifier): Option[User] = |
| for { |
| id <- tryo(openId.getIdentifier()).toOption |
| userAuth <- UserAuth.find(By(UserAuth.authType, moduleName), |
| By(UserAuth.authKey, id)) |
| user <- userAuth.user.obj |
| } yield user |
| } |
| |
| trait LDAPBase { |
| |
| this : AuthModule => |
| |
| object myLdapVendor extends LDAPVendor |
| |
| val rolesToCheck = Props.get("role.list") match { |
| case Full(s) => s.split(',').toList |
| case _ => Nil |
| } |
| |
| var currentRole : String = _ |
| |
| def myLdap : LDAPVendor = { |
| val ldapSrvHost = Props.get("ldap.server.host") openOr "" |
| debug("LDAP server host: %s".format(ldapSrvHost)) |
| val ldapSrvPort = Props.get("ldap.server.port") openOr "" |
| debug("LDAP server port: %s".format(ldapSrvPort)) |
| val ldapSrvBase = Props.get("ldap.server.base") openOr "" |
| debug("LDAP server base: %s".format(ldapSrvBase)) |
| val ldapSrvUsrName = Props.get("ldap.server.userName") openOr "" |
| debug("LDAP server username: %s".format(ldapSrvUsrName)) |
| val ldapSrvPwd = Props.get("ldap.server.password") openOr "" |
| debug("LDAP server password: %s".format(ldapSrvPwd)) |
| val ldapSrvAuthType = Props.get("ldap.server.authType") openOr "" |
| debug("LDAP server authentication type: %s".format(ldapSrvAuthType)) |
| val ldapSrvReferral= Props.get("ldap.server.referral") openOr "" |
| debug("LDAP server referral: %s".format(ldapSrvReferral)) |
| val ldapSrvCtxFactory = Props.get("ldap.server.initial_context_factory") openOr "" |
| debug("LDAP server initial context factory class: %s".format(ldapSrvCtxFactory)) |
| |
| |
| myLdapVendor.configure(Map("ldap.url" -> "ldap://%s:%s".format(ldapSrvHost, ldapSrvPort), |
| "ldap.base" -> ldapSrvBase, |
| "ldap.userName" -> ldapSrvUsrName, |
| "ldap.password" -> ldapSrvPwd, |
| "ldap.authType" -> ldapSrvAuthType, |
| "referral" -> ldapSrvReferral, |
| "ldap.initial_context_factory" -> ldapSrvCtxFactory)) |
| myLdapVendor |
| } |
| |
| 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) { |
| while(allAttrs.hasMore()) { |
| val attribute = allAttrs.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 |
| } |
| attrsMap += (attribute.getID() -> attrValues) |
| } |
| } |
| } |
| attrsMap |
| } |
| |
| def constructDistinguishedName(who : String, isGroup : Boolean = false) = { |
| val base = Props.get( if(isGroup) {"ldap.groupBase"} else {"ldap.userBase"} ) openOr "" |
| debug("LDAP base: %s".format(base)) |
| |
| val dn = "%s,%s".format(constructNameWithPrefix(who, isGroup), base) |
| debug("Distinguished name: %s".format(dn)) |
| dn |
| } |
| |
| def constructNameWithPrefix(username: String, isGroup: Boolean = false) = { |
| val prefix = if(isGroup) {"cn"} else {Props.get("ldap.uidPrefix") openOr ""} |
| val nameWithPrefix = "%s=%s".format(prefix, username) |
| debug("Name with prefix: '%s'".format(nameWithPrefix)) |
| nameWithPrefix |
| } |
| |
| def logInUser(who: User) { |
| User.logUserIn(who) |
| User.setRole(currentRole) |
| S.notice(S.?("base_user_msg_welcome", who.niceName)) |
| } |
| } |
| |
| object ContainerManagedAuthModule extends AuthModule with LDAPBase { |
| |
| override def isDefault = false |
| |
| def loginPresentation: Box[NodeSeq] = Empty |
| |
| def moduleName: String = "cm" |
| |
| val cmaPath = Props.get("cma.path") match { |
| case Full(s) => s.split('/').toList |
| case _ => List("cm", "login") |
| } |
| |
| def performInit(): Unit = { |
| |
| LiftRules.dispatch.append { |
| case Req(`cmaPath`, _, _) => { |
| val from = "/" |
| |
| S.request match { |
| case Full(req) => { |
| val httpRequest: HTTPRequest = req.request |
| val hrs = httpRequest.asInstanceOf[HTTPRequestServlet] |
| val hsr: HttpServletRequest = hrs.req |
| val username : String = 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 |
| } |
| UserAuth.create.authType(moduleName).user(usr).authKey(username).save |
| logInUser(usr) |
| } |
| } |
| }) |
| } |
| } else { |
| S.error(S.?("base_user_err_unknown_creds")) |
| } |
| |
| } |
| case Empty => { |
| S.error(S.?("base_user_err_unknown_creds")) |
| } |
| } |
| |
| S.redirectTo(from) |
| } |
| } |
| } |
| |
| def createHolder(): FieldSet = new FieldSet { |
| def toForm: NodeSeq = NodeSeq.Empty |
| def validate: List[FieldError] = Nil |
| def save(user: User): Unit = {} |
| } |
| } |
| |
| object LDAPAuthModule extends AuthModule with LDAPBase { |
| |
| override def isDefault = false |
| |
| def loginPresentation: Box[NodeSeq] = TemplateFinder.findAnyTemplate("templates-hidden" :: "ldap_login_form" :: Nil) |
| |
| def moduleName: String = "ldap" |
| |
| def performInit(): Unit = { |
| LiftRules.dispatch.append { |
| case Req("ldap" :: "login" :: Nil, _, PostRequest) => |
| val from = S.referer openOr "/" |
| |
| val ldapEnabled = Props.getBool("ldap.enabled") openOr false |
| 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))) { |
| (for { |
| user <- UserAuth.find(By(UserAuth.authKey, name), |
| By(UserAuth.authType, moduleName)).flatMap(_.user.obj) or |
| User.find(By(User.nickname, name)) |
| } yield user) match { |
| case Full(user) => |
| debug("User: '%s' has been found".format(user.niceName)) |
| logInUser(user) |
| 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 |
| UserAuth.create.authType(moduleName).user(usr).authKey(name).save |
| logInUser(usr) |
| } |
| } else { |
| S.error(S.?("base_user_err_unknown_creds")) |
| } |
| } |
| |
| S.redirectTo(from) |
| } |
| } |
| |
| 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 |
| } |
| } |
| debug("No roles have been found") |
| return false; |
| } |
| |
| def createHolder(): FieldSet = new FieldSet { |
| def toForm: NodeSeq = NodeSeq.Empty |
| def validate: List[FieldError] = Nil |
| def save(user: User): Unit = {} |
| } |
| } |
| |
| object ESMEOpenIDVendor extends OpenIDVendor { |
| type UserType = User |
| type ConsumerType = ESMEOpenIDConsumer |
| |
| def logUserOut(): Unit = User.logUserOut() |
| |
| def currentUser = User.currentUser |
| |
| def postLogin(id: Box[Identifier],res: VerificationResult): Unit = { |
| id match { |
| case Full(OpenIDAuthModule(user)) => |
| // val user: User = OpenIDAuthModule.findOrCreate(id.getIdentifier()) |
| User.logUserIn(user) |
| S.notice(S.?("base_user_msg_welcome",user.niceName)) |
| |
| case Full(id) => |
| S.error(S.?("base_user_err_openid_not_reg",id.getIdentifier())) |
| |
| |
| case _ => |
| logUserOut() |
| S.error(S.?("base_user_err_no_auth")) |
| } |
| } |
| |
| def displayUser(in: User): NodeSeq = Text(S.?("base_user_msg_welcome",User.niceName)) |
| |
| def createAConsumer = new ESMEOpenIDConsumer |
| } |
| |
| class ESMEOpenIDConsumer extends OpenIDConsumer[User] with Loggable |
| { |
| override val manager = { |
| |
| logger.info("Proxy settings: " + Props.get("http.proxyHost", "[no host]") |
| + ":" + Props.get("http.proxyPort", "[no port]")) |
| |
| for (host <- Props.get("http.proxyHost")){ |
| val proxyProps = new ProxyProperties() |
| proxyProps.setProxyHostName(host) |
| proxyProps.setProxyPort(Props.getInt("http.proxyPort", 80)) |
| HttpClientFactory.setProxyProperties(proxyProps) |
| } |
| new ConsumerManager |
| } |
| } |