All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.liftweb.proto.ProtoUser.scala Maven / Gradle / Ivy

There is a newer version: 3.5.0
Show newest version
/*
 * Copyright 2006-2011 WorldWide Conferencing, LLC
 *
 * Licensed 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 net.liftweb
package proto

import net.liftweb.http._
import js._
import JsCmds._
import scala.xml.{NodeSeq, Node, Text, Elem}
import scala.xml.transform._
import net.liftweb.sitemap._
import net.liftweb.sitemap.Loc._
import net.liftweb.util.Helpers._
import net.liftweb.util._
import net.liftweb.common._
import net.liftweb.util.Mailer._
import S._

/**
 * A prototypical user class with abstractions to the underlying storage
 */
trait ProtoUser {
  /**
   * The underlying record for the User
   */
  type TheUserType

  /**
   * Bridges from TheUserType to methods used in this class
   */
  protected trait UserBridge {
    /**
     * Convert the user's primary key to a String
     */
    def userIdAsString: String

    /**
     * Return the user's first name
     */
    def getFirstName: String

    /**
     * Return the user's last name
     */
    def getLastName: String

    /**
     * Get the user's email
     */
    def getEmail: String

    /**
     * Is the user a superuser
     */
    def superUser_? : Boolean

    /**
     * Has the user been validated?
     */
    def validated_? : Boolean

    /**
     * Does the supplied password match the actual password?
     */
    def testPassword(toTest: Box[String]): Boolean

    /**
     * Set the validation flag on the user and return the user
     */
    def setValidated(validation: Boolean): TheUserType

    /**
     * Set the unique ID for this user to a new value
     */
    def resetUniqueId(): TheUserType

    /**
     * Return the unique ID for the user
     */
    def getUniqueId(): String

    /**
     * Validate the user
     */
    def validate: List[FieldError]

    /**
     * Given a list of string, set the password
     */
    def setPasswordFromListString(in: List[String]): TheUserType

    /**
     * Save the user to backing store
     */
    def save: Boolean

    /**
     * Get a nice name for the user
     */
    def niceName: String = (getFirstName, getLastName, getEmail) match {
      case (f, l, e) if f.length > 1 && l.length > 1 => f+" "+l+" ("+e+")"
      case (f, _, e) if f.length > 1 => f+" ("+e+")"
      case (_, l, e) if l.length > 1 => l+" ("+e+")"
      case (_, _, e) => e
    }
    
    /**
     * Get a short name for the user
     */
    def shortName: String = (getFirstName, getLastName) match {
      case (f, l) if f.length > 1 && l.length > 1 => f+" "+l
      case (f, _) if f.length > 1 => f
      case (_, l) if l.length > 1 => l
      case _ => getEmail
    }
    
    /**
     * Get an email link
     */
    def niceNameWEmailLink = {niceName}

  }

  /**
   * Convert an instance of TheUserType to the Bridge trait
   */
  protected implicit def typeToBridge(in: TheUserType): UserBridge

  /**
   * Get a nice name for the user
   */
  def niceName(inst: TheUserType): String = inst.niceName

  /**
   * Get a nice name for the user
   */
  def shortName(inst: TheUserType): String = inst.shortName

  /**
   * Get an email link for the user
   */
  def niceNameWEmailLink(inst: TheUserType): Elem = inst.niceNameWEmailLink

  /**
   * A generic representation of a field.  For example, this represents the
   * abstract "name" field and is used along with an instance of TheCrudType
   * to compute the BaseField that is the "name" field on the specific instance
   * of TheCrudType
   */
  type FieldPointerType

  /**
   * Based on a FieldPointer, build a FieldPointerBridge
   */
  protected implicit def buildFieldBridge(from: FieldPointerType): FieldPointerBridge
  
  protected trait FieldPointerBridge {
    /**
     * What is the display name of this field?
     */
    def displayHtml: NodeSeq

    /**
     * Does this represent a pointer to a Password field
     */
    def isPasswordField_? : Boolean
  }

  /**
   * The list of fields presented to the user at sign-up
   */
  def signupFields: List[FieldPointerType]


  /**
   * The list of fields presented to the user for editing
   */
  def editFields: List[FieldPointerType]

  /**
   * What template are you going to wrap the various nodes in
   */
  def screenWrap: Box[Node] = Empty

  /**
   * The base path for the user related URLs.  Override this
   * method to change the base path
   */
  def basePath: List[String] = "user_mgt" :: Nil

  /**
   * The path suffix for the sign up screen
   */
  def signUpSuffix: String = "sign_up"

  /**
   * The computed path for the sign up screen
   */
  lazy val signUpPath = thePath(signUpSuffix)

  /**
   * The path suffix for the login screen
   */
  def loginSuffix = "login"

  /**
   * The computed path for the login screen
   */
  lazy val loginPath = thePath(loginSuffix)

  /**
   * The path suffix for the lost password screen
   */
  def lostPasswordSuffix = "lost_password"

  /**
   * The computed path for the lost password screen
   */
  lazy val lostPasswordPath = thePath(lostPasswordSuffix)

  /**
   * The path suffix for the reset password screen
   */
  def passwordResetSuffix = "reset_password"

  /**
   * The computed path for the reset password screen
   */
  lazy val passwordResetPath = thePath(passwordResetSuffix)

  /**
   * The path suffix for the change password screen
   */
  def changePasswordSuffix = "change_password"

  /**
   * The computed path for change password screen
   */
  lazy val changePasswordPath = thePath(changePasswordSuffix)

  /**
   * The path suffix for the logout screen
   */
  def logoutSuffix = "logout"

  /**
   * The computed pat for logout
   */
  lazy val logoutPath = thePath(logoutSuffix)

  /**
   * The path suffix for the edit screen
   */
  def editSuffix = "edit"

  /**
   * The computed path for the edit screen
   */
  lazy val editPath = thePath(editSuffix)

  /**
   * The path suffix for the validate user screen
   */
  def validateUserSuffix = "validate_user"

  /**
   * The calculated path to the user validation screen
   */
  lazy val validateUserPath = thePath(validateUserSuffix)

  /**
   * The application's home page
   */
  def homePage = "/"

  /**
   * If you want to redirect a user to a different page after login,
   * put the page here
   */
  object loginRedirect extends SessionVar[Box[String]](Empty) {
    override lazy val __nameSalt = Helpers.nextFuncName
  }

  /**
   * A helper class that holds menu items for the path
   */
  case class MenuItem(name: String, path: List[String],
                      loggedIn: Boolean) {
    lazy val endOfPath = path.last
    lazy val pathStr: String = path.mkString("/", "/", "")
    lazy val display = name match {
      case null | "" => false
      case _ => true
    }
  }

  /**
   * Calculate the path given a suffix by prepending the basePath to the suffix
   */
  protected def thePath(end: String): List[String] = basePath ::: List(end)

  /**
   * Return the URL of the "login" page
   */
  def loginPageURL = loginPath.mkString("/","/", "")

  /**
   * Inverted loggedIn_?
   */
  def notLoggedIn_? = !loggedIn_?

  /**
   * A Menu.LocParam to test if the user is logged in
   */
  lazy val testLogginIn = If(loggedIn_? _, S.?("must.be.logged.in")) ;

  /**
   * A Menu.LocParam to test if the user is a super user
   */
  lazy val testSuperUser = If(superUser_? _, S.?("must.be.super.user"))

  /**
   * A Menu.LocParam for testing if the user is logged in and if they're not,
   * redirect them to the login page
   */
  def loginFirst = If(
    loggedIn_? _,
    () => {
      import net.liftweb.http.{RedirectWithState, RedirectState}
      val uri = S.uriAndQueryString
      RedirectWithState(
        loginPageURL,
        RedirectState( ()=>{loginRedirect.set(uri)})
      )
    }
  )

  /**
   * Is there a user logged in and are they a superUser?
   */
  def superUser_? : Boolean = currentUser.map(_.superUser_?) openOr false

  /**
   * The menu item for login (make this "Empty" to disable)
   */
  def loginMenuLoc: Box[Menu] =
    Full(Menu(Loc("Login" + menuNameSuffix, loginPath, S.?("login"), loginMenuLocParams ::: globalUserLocParams)))


  /**
   * If you want to include a LocParam (e.g. LocGroup) on all the
   * User menus, add them here
   */
  protected def globalUserLocParams: List[LocParam[Unit]] = Nil

  /**
   * The LocParams for the menu item for login.
   * Overwrite in order to add custom LocParams. Attention: Not calling super will change the default behavior!
   */
  protected def loginMenuLocParams: List[LocParam[Unit]] =
    If(notLoggedIn_? _, S.?("already.logged.in")) ::
    Template(() => wrapIt(login)) ::
    Nil

  /**
   * If you have more than 1 ProtoUser in your application, you'll need to distinguish the menu names.
   * Do so by changing the menu name suffix so that there are no name clashes
   */
  protected def menuNameSuffix: String = ""

  /**
   * The menu item for logout (make this "Empty" to disable)
   */
  def logoutMenuLoc: Box[Menu] =
    Full(Menu(Loc("Logout" + menuNameSuffix, logoutPath, S.?("logout"), logoutMenuLocParams ::: globalUserLocParams)))

  /**
   * The LocParams for the menu item for logout.
   * Overwrite in order to add custom LocParams. Attention: Not calling super will change the default behavior!
   */
  protected def logoutMenuLocParams: List[LocParam[Unit]] =
    Template(() => wrapIt(logout)) ::
    testLogginIn ::
    Nil

  /**
   * The menu item for creating the user/sign up (make this "Empty" to disable)
   */
  def createUserMenuLoc: Box[Menu] =
    Full(Menu(Loc("CreateUser" + menuNameSuffix, signUpPath, S.?("sign.up"), createUserMenuLocParams ::: globalUserLocParams)))

  /**
   * The LocParams for the menu item for creating the user/sign up.
   * Overwrite in order to add custom LocParams. Attention: Not calling super will change the default behavior!
   */
  protected def createUserMenuLocParams: List[LocParam[Unit]] =
    Template(() => wrapIt(signupFunc.map(_()) openOr signup)) ::
    If(notLoggedIn_? _, S.?("logout.first")) ::
    Nil

  /**
   * The menu item for lost password (make this "Empty" to disable)
   */
  def lostPasswordMenuLoc: Box[Menu] =
    Full(Menu(Loc("LostPassword" + menuNameSuffix, lostPasswordPath, S.?("lost.password"), lostPasswordMenuLocParams ::: globalUserLocParams))) // not logged in

  /**
   * The LocParams for the menu item for lost password.
   * Overwrite in order to add custom LocParams. Attention: Not calling super will change the default behavior!
   */
  protected def lostPasswordMenuLocParams: List[LocParam[Unit]] =
    Template(() => wrapIt(lostPassword)) ::
    If(notLoggedIn_? _, S.?("logout.first")) ::
    Nil

  /**
   * The menu item for resetting the password (make this "Empty" to disable)
   */
  def resetPasswordMenuLoc: Box[Menu] =
    Full(Menu(Loc("ResetPassword" + menuNameSuffix, (passwordResetPath, true), S.?("reset.password"), resetPasswordMenuLocParams ::: globalUserLocParams))) //not Logged in

  /**
   * The LocParams for the menu item for resetting the password.
   * Overwrite in order to add custom LocParams. Attention: Not calling super will change the default behavior!
   */
  protected def resetPasswordMenuLocParams: List[LocParam[Unit]] =
    Hidden ::
    Template(() => wrapIt(passwordReset(snarfLastItem))) ::
    If(notLoggedIn_? _, S.?("logout.first")) ::
    Nil

  /**
   * The menu item for editing the user (make this "Empty" to disable)
   */
  def editUserMenuLoc: Box[Menu] =
    Full(Menu(Loc("EditUser" + menuNameSuffix, editPath, S.?("edit.user"), editUserMenuLocParams ::: globalUserLocParams)))

  /**
   * The LocParams for the menu item for editing the user.
   * Overwrite in order to add custom LocParams. Attention: Not calling super will change the default behavior!
   */
  protected def editUserMenuLocParams: List[LocParam[Unit]] =
    Template(() => wrapIt(editFunc.map(_()) openOr edit)) ::
    testLogginIn ::
    Nil

  /**
   * The menu item for changing password (make this "Empty" to disable)
   */
  def changePasswordMenuLoc: Box[Menu] =
    Full(Menu(Loc("ChangePassword" + menuNameSuffix, changePasswordPath, S.?("change.password"), changePasswordMenuLocParams ::: globalUserLocParams)))

  /**
   * The LocParams for the menu item for changing password.
   * Overwrite in order to add custom LocParams. Attention: Not calling super will change the default behavior!
   */
  protected def changePasswordMenuLocParams: List[LocParam[Unit]] =
    Template(() => wrapIt(changePassword)) ::
    testLogginIn ::
    Nil

  /**
   * The menu item for validating a user (make this "Empty" to disable)
   */
  def validateUserMenuLoc: Box[Menu] =
    Full(Menu(Loc("ValidateUser" + menuNameSuffix, (validateUserPath, true), S.?("validate.user"), validateUserMenuLocParams ::: globalUserLocParams)))

  /**
   * The LocParams for the menu item for validating a user.
   * Overwrite in order to add custom LocParams. Attention: Not calling super will change the default behavior!
   */
  protected def validateUserMenuLocParams: List[LocParam[Unit]] =
    Hidden ::
    Template(() => wrapIt(validateUser(snarfLastItem))) ::
    If(notLoggedIn_? _, S.?("logout.first")) ::
    Nil

  /**
   * An alias for the sitemap property
   */
  def menus: List[Menu] = sitemap // issue 182

  /**
   * Insert this LocParam into your menu if you want the
   * User's menu items to be inserted at the same level
   * and after the item
   */
  final case object AddUserMenusAfter extends Loc.LocParam[Any]

  /**
   * replace the menu that has this LocParam with the User's menu
   * items
   */
  final case object AddUserMenusHere extends Loc.LocParam[Any]

  /**
   * Insert this LocParam into your menu if you want the
   * User's menu items to be children of that menu
   */
  final case object AddUserMenusUnder extends Loc.LocParam[Any]

  private lazy val AfterUnapply = SiteMap.buildMenuMatcher(_ == AddUserMenusAfter)
  private lazy val HereUnapply = SiteMap.buildMenuMatcher(_ == AddUserMenusHere)
  private lazy val UnderUnapply = SiteMap.buildMenuMatcher(_ == AddUserMenusUnder)

  /**
   * The SiteMap mutator function
   */
  def sitemapMutator: SiteMap => SiteMap = SiteMap.sitemapMutator {
    case AfterUnapply(menu) => menu :: sitemap
    case HereUnapply(_) => sitemap
    case UnderUnapply(menu) => List(menu.rebuild(_ ::: sitemap))
  }(SiteMap.addMenusAtEndMutator(sitemap))

  lazy val sitemap: List[Menu] =
  List(loginMenuLoc, createUserMenuLoc,
       lostPasswordMenuLoc, resetPasswordMenuLoc,
       editUserMenuLoc, changePasswordMenuLoc,
       validateUserMenuLoc, logoutMenuLoc).flatten(a => a)


  def skipEmailValidation = false

  def userMenu: List[Node] = {
    val li = loggedIn_?
    ItemList.
    filter(i => i.display && i.loggedIn == li).
    map(i => ({i.name}))
  }

  protected def snarfLastItem: String =
  (for (r <- S.request) yield r.path.wholePath.last) openOr ""

  lazy val ItemList: List[MenuItem] =
  List(MenuItem(S.?("sign.up"), signUpPath, false),
       MenuItem(S.?("log.in"), loginPath, false),
       MenuItem(S.?("lost.password"), lostPasswordPath, false),
       MenuItem("", passwordResetPath, false),
       MenuItem(S.?("change.password"), changePasswordPath, true),
       MenuItem(S.?("log.out"), logoutPath, true),
       MenuItem(S.?("edit.profile"), editPath, true),
       MenuItem("", validateUserPath, false))

  var onLogIn: List[TheUserType => Unit] = Nil

  var onLogOut: List[Box[TheUserType] => Unit] = Nil

  /**
   * This function is given a chance to log in a user
   * programmatically when needed
   */
  var autologinFunc: Box[()=>Unit] = Empty

  def loggedIn_? = {
    if(!currentUserId.isDefined)
      for(f <- autologinFunc) f()
    currentUserId.isDefined
  }

  def logUserIdIn(id: String) {
    curUser.remove()
    curUserId(Full(id))
  }

  def logUserIn(who: TheUserType, postLogin: () => Nothing): Nothing = {
    if (destroySessionOnLogin) {
      S.session.openOrThrowException("we have a session here").destroySessionAndContinueInNewSession(() => {
        logUserIn(who)
        postLogin()
      })
    } else {
      logUserIn(who)
      postLogin()
    }
  }

  def logUserIn(who: TheUserType) {
    curUserId.remove()
    curUser.remove()
    curUserId(Full(who.userIdAsString))
    curUser(Full(who))
    onLogIn.foreach(_(who))
  }

  def logoutCurrentUser = logUserOut()

  def logUserOut() {
    onLogOut.foreach(_(curUser))
    curUserId.remove()
    curUser.remove()
    S.session.foreach(_.destroySession())
  }

  /**
   * There may be times when you want to be another user
   * for some stack frames.  Here's how to do it.
   */
  def doWithUser[T](u: Box[TheUserType])(f: => T): T =
    curUserId.doWith(u.map(_.userIdAsString)) {
      curUser.doWith(u) {
        f
      }
    }


  private object curUserId extends SessionVar[Box[String]](Empty) {
    override lazy val __nameSalt = Helpers.nextFuncName
  }


  def currentUserId: Box[String] = curUserId.get

  private object curUser extends RequestVar[Box[TheUserType]](currentUserId.flatMap(userFromStringId))  with CleanRequestVarOnSessionTransition  {
    override lazy val __nameSalt = Helpers.nextFuncName
  }


  /**
   * Given a String representing the User ID, find the user
   */
  protected def userFromStringId(id: String): Box[TheUserType]

  def currentUser: Box[TheUserType] = curUser.get

  def signupXhtml(user: TheUserType) = {
    (
{localForm(user, false, signupFields)}
{ S.?("sign.up") }
 
) } def signupMailBody(user: TheUserType, validationLink: String): Elem = { ( {S.?("sign.up.confirmation")}

{S.?("dear")} {user.getFirstName},

{S.?("sign.up.validation.link")}
{validationLink}

{S.?("thank.you")}

) } def signupMailSubject = S.?("sign.up.confirmation") /** * Send validation email to the user. The XHTML version of the mail * body is generated by calling signupMailBody. You can customize the * mail sent to users by override generateValidationEmailBodies to * send non-HTML mail or alternative mail bodies. */ def sendValidationEmail(user: TheUserType) { val resetLink = S.hostAndPath+"/"+validateUserPath.mkString("/")+ "/"+urlEncode(user.getUniqueId()) val email: String = user.getEmail val msgXml = signupMailBody(user, resetLink) Mailer.sendMail(From(emailFrom),Subject(signupMailSubject), (To(user.getEmail) :: generateValidationEmailBodies(user, resetLink) ::: (bccEmail.toList.map(BCC(_)))) :_* ) } /** * Generate the mail bodies to send with the valdiation link. * By default, just an HTML mail body is generated by calling signupMailBody * but you can send additional or alternative mail by override this method. */ protected def generateValidationEmailBodies(user: TheUserType, resetLink: String): List[MailBodyType] = List(xmlToMailBodyType(signupMailBody(user, resetLink))) protected object signupFunc extends RequestVar[Box[() => NodeSeq]](Empty) { override lazy val __nameSalt = Helpers.nextFuncName } /** * Override this method to do something else after the user signs up */ protected def actionsAfterSignup(theUser: TheUserType, func: () => Nothing): Nothing = { theUser.setValidated(skipEmailValidation).resetUniqueId() theUser.save if (!skipEmailValidation) { sendValidationEmail(theUser) S.notice(S.?("sign.up.message")) func() } else { logUserIn(theUser, () => { S.notice(S.?("welcome")) func() }) } } /** * Override this method to validate the user signup (eg by adding captcha verification) */ def validateSignup(user: TheUserType): List[FieldError] = user.validate /** * Create a new instance of the User */ protected def createNewUserInstance(): TheUserType /** * If there's any mutation to do to the user on creation for * signup, override this method and mutate the user. This can * be used to pull query parameters from the request and assign * certain fields. . Issue #722 * * @param user the user to mutate * @return the mutated user */ protected def mutateUserOnSignup(user: TheUserType): TheUserType = user def signup = { val theUser: TheUserType = mutateUserOnSignup(createNewUserInstance()) val theName = signUpPath.mkString("") def testSignup() { validateSignup(theUser) match { case Nil => actionsAfterSignup(theUser, () => S.redirectTo(homePage)) case xs => S.error(xs) ; signupFunc(Full(innerSignup _)) } } def innerSignup = { ("type=submit" #> signupSubmitButton(S ? "sign.up", testSignup _)) apply signupXhtml(theUser) } innerSignup } def signupSubmitButton(name: String, func: () => Any = () => {}): NodeSeq = { standardSubmitButton(name, func) } def emailFrom = "noreply@"+S.hostName def bccEmail: Box[String] = Empty def testLoggedIn(page: String): Boolean = ItemList.filter(_.endOfPath == page) match { case x :: xs if x.loggedIn == loggedIn_? => true case _ => false } def validateUser(id: String): NodeSeq = findUserByUniqueId(id) match { case Full(user) if !user.validated_? => user.setValidated(true).resetUniqueId().save logUserIn(user, () => { S.notice(S.?("account.validated")) S.redirectTo(homePage) }) case _ => S.error(S.?("invalid.validation.link")); S.redirectTo(homePage) } /** * How do we prompt the user for the username. By default, * it's S.?("email.address"), you can can change it to something else */ def userNameFieldString: String = S.?("email.address") /** * The string that's generated when the user name is not found. By * default: S.?("email.address.not.found") */ def userNameNotFoundString: String = S.?("email.address.not.found") def loginXhtml = { (
{S.?("log.in")}
{userNameFieldString}
{S.?("password")}
{S.?("recover.password")}
) } /** * Given an username (probably email address), find the user */ protected def findUserByUserName(username: String): Box[TheUserType] /** * Given a unique id, find the user */ protected def findUserByUniqueId(id: String): Box[TheUserType] /** * By default, destroy the session on login. * Change this is some of the session information needs to * be preserved. */ protected def destroySessionOnLogin = true /** * If there's any state that you want to capture pre-login * to be set post-login (the session is destroyed), * then set the state here. Just make a function * that captures the state... that function will be applied * post login. */ protected def capturePreLoginState(): () => Unit = () => {} def login = { if (S.post_?) { S.param("username"). flatMap(username => findUserByUserName(username)) match { case Full(user) if user.validated_? && user.testPassword(S.param("password")) => { val preLoginState = capturePreLoginState() val redir = loginRedirect.get match { case Full(url) => loginRedirect(Empty) url case _ => homePage } logUserIn(user, () => { S.notice(S.?("logged.in")) preLoginState() S.redirectTo(redir) }) } case Full(user) if !user.validated_? => S.error(S.?("account.validation.error")) case _ => S.error(S.?("invalid.credentials")) } } val emailElemId = nextFuncName S.appendJs(Focus(emailElemId)) val bind = ".email [id]" #> emailElemId & ".email [name]" #> "username" & ".password [name]" #> "password" & "type=submit" #> loginSubmitButton(S.?("log.in")) bind(loginXhtml) } def loginSubmitButton(name: String, func: () => Any = () => {}): NodeSeq = { standardSubmitButton(name, func) } def standardSubmitButton(name: String, func: () => Any = () => {}) = { SHtml.submit(name, func) } def lostPasswordXhtml = { (
{S.?("enter.email")}
{userNameFieldString}
 
) } def passwordResetMailBody(user: TheUserType, resetLink: String): Elem = { ( {S.?("reset.password.confirmation")}

{S.?("dear")} {user.getFirstName},

{S.?("click.reset.link")}
{resetLink}

{S.?("thank.you")}

) } /** * Generate the mail bodies to send with the password reset link. * By default, just an HTML mail body is generated by calling * passwordResetMailBody * but you can send additional or alternative mail by overriding this method. */ protected def generateResetEmailBodies(user: TheUserType, resetLink: String): List[MailBodyType] = List(xmlToMailBodyType(passwordResetMailBody(user, resetLink))) def passwordResetEmailSubject = S.?("reset.password.request") /** * Send password reset email to the user. The XHTML version of the mail * body is generated by calling passwordResetMailBody. You can customize the * mail sent to users by overriding generateResetEmailBodies to * send non-HTML mail or alternative mail bodies. */ def sendPasswordReset(email: String) { findUserByUserName(email) match { case Full(user) if user.validated_? => user.resetUniqueId().save val resetLink = S.hostAndPath+ passwordResetPath.mkString("/", "/", "/")+urlEncode(user.getUniqueId()) val email: String = user.getEmail Mailer.sendMail(From(emailFrom),Subject(passwordResetEmailSubject), (To(user.getEmail) :: generateResetEmailBodies(user, resetLink) ::: (bccEmail.toList.map(BCC(_)))) :_*) S.notice(S.?("password.reset.email.sent")) S.redirectTo(homePage) case Full(user) => sendValidationEmail(user) S.notice(S.?("account.validation.resent")) S.redirectTo(homePage) case _ => S.error(userNameNotFoundString) } } def lostPassword = { val bind = ".email" #> SHtml.text("", sendPasswordReset _) & "type=submit" #> lostPasswordSubmitButton(S.?("send.it")) bind(lostPasswordXhtml) } def lostPasswordSubmitButton(name: String, func: () => Any = () => {}): NodeSeq = { standardSubmitButton(name, func) } def passwordResetXhtml = { (
{S.?("reset.your.password")}
{S.?("enter.your.new.password")}
{S.?("repeat.your.new.password")}
 
) } def passwordReset(id: String) = findUserByUniqueId(id) match { case Full(user) => def finishSet() { user.validate match { case Nil => S.notice(S.?("password.changed")) user.resetUniqueId().save logUserIn(user, () => S.redirectTo(homePage)) case xs => S.error(xs) } } val passwordInput = SHtml.password_*("", (p: List[String]) => user.setPasswordFromListString(p)) val bind = { "type=password" #> passwordInput & "type=submit" #> resetPasswordSubmitButton(S.?("set.password"), finishSet _) } bind(passwordResetXhtml) case _ => S.error(S.?("password.link.invalid")); S.redirectTo(homePage) } def resetPasswordSubmitButton(name: String, func: () => Any = () => {}): NodeSeq = { standardSubmitButton(name, func) } def changePasswordXhtml = { (
{S.?("change.password")}
{S.?("old.password")}
{S.?("new.password")}
{S.?("repeat.password")}
 
) } def changePassword = { val user = currentUser.openOrThrowException("we can do this because the logged in test has happened") var oldPassword = "" var newPassword: List[String] = Nil def testAndSet() { if (!user.testPassword(Full(oldPassword))) S.error(S.?("wrong.old.password")) else { user.setPasswordFromListString(newPassword) user.validate match { case Nil => user.save; S.notice(S.?("password.changed")); S.redirectTo(homePage) case xs => S.error(xs) } } } val bind = { // Use the same password input for both new password fields. val passwordInput = SHtml.password_*("", LFuncHolder(s => newPassword = s)) ".old-password" #> SHtml.password("", s => oldPassword = s) & ".new-password" #> passwordInput & "type=submit" #> changePasswordSubmitButton(S.?("change"), testAndSet _) } bind(changePasswordXhtml) } def changePasswordSubmitButton(name: String, func: () => Any = () => {}): NodeSeq = { standardSubmitButton(name, func) } def editXhtml(user: TheUserType) = { (
{localForm(user, true, editFields)}
{S.?("edit")}
 
) } object editFunc extends RequestVar[Box[() => NodeSeq]](Empty) { override lazy val __nameSalt = Helpers.nextFuncName } /** * If there's any mutation to do to the user on retrieval for * editing, override this method and mutate the user. This can * be used to pull query parameters from the request and assign * certain fields. Issue #722 * * @param user the user to mutate * @return the mutated user */ protected def mutateUserOnEdit(user: TheUserType): TheUserType = user def edit = { val theUser: TheUserType = mutateUserOnEdit(currentUser.openOrThrowException("we know we're logged in")) val theName = editPath.mkString("") def testEdit() { theUser.validate match { case Nil => theUser.save S.notice(S.?("profile.updated")) S.redirectTo(homePage) case xs => S.error(xs) ; editFunc(Full(innerEdit _)) } } def innerEdit = { ("type=submit" #> editSubmitButton(S.?("save"), testEdit _)) apply editXhtml(theUser) } innerEdit } def editSubmitButton(name: String, func: () => Any = () => {}): NodeSeq = { standardSubmitButton(name, func) } def logout = { logoutCurrentUser S.redirectTo(homePage) } /** * Given an instance of TheCrudType and FieldPointerType, convert * that to an actual instance of a BaseField on the instance of TheCrudType */ protected def computeFieldFromPointer(instance: TheUserType, pointer: FieldPointerType): Box[BaseField] protected def localForm(user: TheUserType, ignorePassword: Boolean, fields: List[FieldPointerType]): NodeSeq = { for { pointer <- fields field <- computeFieldFromPointer(user, pointer).toList if field.show_? && (!ignorePassword || !pointer.isPasswordField_?) form <- field.toForm.toList } yield {field.displayName}{form} } protected def wrapIt(in: NodeSeq): NodeSeq = screenWrap.map(new RuleTransformer(new RewriteRule { override def transform(n: Node) = n match { case e: Elem if "bind" == e.label && "lift" == e.prefix => in case _ => n } })) openOr in }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy