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

net.liftweb.mapper.MappedPassword.scala Maven / Gradle / Ivy

The 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 mapper

import net.liftweb.util.Helpers._
import net.liftweb.util.FatLazy
import java.sql.{ResultSet, Types}
import java.lang.reflect.Method
import scala.xml.{Node, Text, NodeSeq}
import java.util.Date
import net.liftweb.http.{S}
import net.liftweb.http.S._
import net.liftweb.util._
import net.liftweb.json._
import net.liftweb.common._
import net.liftweb.http.js._

import org.mindrot.jbcrypt.BCrypt

object MappedPassword {
  val blankPw = "*******"

  /**
   * Set this in boot if you want Bcrypt salt strength to be
   * something more than the default
   */
  var bcryptStrength: Box[Int] = None
}

abstract class MappedPassword[T<:Mapper[T]](val fieldOwner: T)
extends MappedField[String, T] {
  override def dbColumnCount = 2
  def dbFieldClass = classOf[String]

  override def dbColumnNames(in : String) = in.toLowerCase+"_pw" :: in.toLowerCase+"_slt" :: Nil

  override lazy val dbSelectString =
  dbColumnNames(name).
  map(cn => fieldOwner.getSingleton._dbTableNameLC + "." + cn).
  mkString(", ")

   def asJsonValue: Box[JsonAST.JValue] = Full(JsonAST.JNull)

  def salt = this.salt_i

  import scala.reflect.runtime.universe._
  def manifest: TypeTag[String] = typeTag[String]

  /**
   * Get the source field metadata for the field
   * @return the source field metadata for the field
   */
  def sourceInfoMetadata(): SourceFieldMetadata{type ST = String} =
    SourceFieldMetadataRep(name, manifest, new FieldConverter {
      /**
       * The type of the field
       */
      type T = String

      /**
       * Convert the field to a String
       * @param v the field value
       * @return the string representation of the field value
       */
      def asString(v: T): String = ""

      /**
       * Convert the field into NodeSeq, if possible
       * @param v the field value
       * @return a NodeSeq if the field can be represented as one
       */
      def asNodeSeq(v: T): Box[NodeSeq] = Empty

      /**
       * Convert the field into a JSON value
       * @param v the field value
       * @return the JSON representation of the field
       */
      def asJson(v: T): Box[JValue] = Empty

      /**
       * If the field can represent a sequence of SourceFields,
       * get that
       * @param v the field value
       * @return the field as a sequence of SourceFields
       */
      def asSeq(v: T): Box[Seq[SourceFieldInfo]] = Empty
    })


  private var password = FatLazy(defaultValue)
  private val salt_i = FatLazy(util.Safe.randomString(16))
  private var invalidPw = false
  private var invalidMsg = ""

  protected def real_i_set_!(value : String) : String = {
    value match {
      case "*" | null | MappedPassword.blankPw if (value.length < 3) =>
       invalidPw = true ; invalidMsg = S.?("password.must.be.set") ; password.set("*")
      case MappedPassword.blankPw => return "*"
      case _ if (value.length > 4) => invalidPw = false;
      val bcrypted = BCrypt.hashpw(value, MappedPassword.bcryptStrength.map(BCrypt.gensalt(_)) openOr BCrypt.gensalt())
      password.set("b;"+bcrypted.substring(0,44))
      salt_i.set(bcrypted.substring(44))
      case _ => invalidPw = true ; invalidMsg = S.?("password.too.short"); password.set("*")
    }
    this.dirty_?( true)
    "*"
  }

  def setList(in: List[String]): Boolean =
  in match {
    case x1 :: x2 :: Nil if x1 == x2 => this.set(x1) ; true
    case _ => invalidPw = true; invalidMsg = S.?("passwords.do.not.match"); false
  }


  override def setFromAny(f: Any): String = {
    f match {
      case a : Array[String] if (a.length == 2 && a(0) == a(1)) =>
        this.set(a(0))
      case l : List[_] if (l.length == 2 && l.head == l(1)) =>
        this.set(l.head.asInstanceOf[String])
      case _ => 
        invalidPw = true
        invalidMsg = S.?("passwords.do.not.match")
    }
    get
  }

  override def renderJs_? = false

  def asJsExp: JsExp = throw new NullPointerException("No way")

  /**
   * Test to see if an incoming password matches
   * @param toMatch the password to test
   * @return the matched value
   */
  def match_?(toMatch : String): Boolean = {
    if (password.get.startsWith("b;")) {
      BCrypt.checkpw(toMatch, password.get.substring(2)+salt_i.get)
    } else
    hash("{"+toMatch+"} salt={"+salt_i.get+"}") == password.get
  }

  override def validate : List[FieldError] = {
    if (!invalidPw && password.get != "*") Nil
    else if (invalidPw) List(FieldError(this, Text(invalidMsg)))
    else List(FieldError(this, Text(S.?("password.must.be.set"))))
  }

  def real_convertToJDBCFriendly(value: String): Object =
    BCrypt.hashpw(value, MappedPassword.bcryptStrength.map(BCrypt.gensalt(_)) openOr BCrypt.gensalt())

  /**
   * Get the JDBC SQL Type for this field
   */
  def targetSQLType = Types.VARCHAR

  def defaultValue = "*"

  override def writePermission_? = true
  override def readPermission_? = true

  protected def i_is_! = MappedPassword.blankPw
  protected def i_was_! = MappedPassword.blankPw
  /**
   * Called after the field is saved to the database
   */
  override protected[mapper] def doneWithSave(): Unit = {
  }

  protected def i_obscure_!(in : String) : String = in

  /**
   * Create an input field for the item
   */
  override def _toForm: Box[NodeSeq] = {
    S.fmapFunc({s: List[String] => this.setFromAny(s)}){funcName =>
      Full({appendFieldId()} {S.?("repeat")} )
    }
  }

  /**
   * When building the form field, what's the input element's
   * type attribute.
   */
  override protected def formInputType = "password"


  def jdbcFriendly(columnName : String) = {
    if (columnName.endsWith("_slt")) {
      salt_i.get
    } else if (columnName.endsWith("_pw")) {
      password.get
    } else {
      null
    }
  }

  def buildSetLongValue(accessor : Method, columnName : String) : (T, Long, Boolean) => Unit = {
    if (columnName.endsWith("_slt")) {
      {(inst : T, v: Long, isNull: Boolean ) => {val tv = getField(inst, accessor).asInstanceOf[MappedPassword[T]]; tv.salt_i() = if (isNull) null else v.toString}}
    } else if (columnName.endsWith("_pw")) {
      {(inst : T, v: Long, isNull: Boolean ) => {val tv = getField(inst, accessor).asInstanceOf[MappedPassword[T]]; tv.password() = if (isNull) null else v.toString}}
    } else {
      null
    }
  }
  def buildSetStringValue(accessor : Method, columnName : String) : (T, String) => Unit  = {
    if (columnName.endsWith("_slt")) {
      {(inst : T, v: String ) => {val tv = getField(inst, accessor).asInstanceOf[MappedPassword[T]]; tv.salt_i() = v}}
    } else if (columnName.endsWith("_pw")) {
      {(inst : T, v: String ) => {val tv = getField(inst, accessor).asInstanceOf[MappedPassword[T]]; tv.password() = v}}
    } else {
      null
    }
  }
  def buildSetDateValue(accessor : Method, columnName : String) : (T, Date) => Unit   = {
    null
  }
  def buildSetBooleanValue(accessor : Method, columnName : String) : (T, Boolean, Boolean) => Unit   = {
    null
  }

  def buildSetActualValue(accessor : Method, inst : AnyRef, columnName : String) : (T, AnyRef) => Unit = {
    if (columnName.endsWith("_slt")) {
      inst match {
        case null => {(inst : T, v : AnyRef) => {}}
        case _ => {(inst : T, v : AnyRef) => {val tv = getField(inst, accessor).asInstanceOf[MappedPassword[T]]; tv.salt_i() = (if (v == null) null else v.toString); tv.resetDirty}}
      }
    } else if (columnName.endsWith("_pw")) {
      inst match {
        case null => {(inst : T, v : AnyRef) => {}}
        case _ => {(inst : T, v : AnyRef) => {val tv = getField(inst, accessor).asInstanceOf[MappedPassword[T]]; tv.password() = (if (v == null) null else v.toString); tv.resetDirty}}
      }

    } else {
      null
    }
  }

  /**
   * Given the driver type, return the string required to create the column in the database
   */
  def fieldCreatorString(dbType: DriverType, colName: String): String = (if (colName.endsWith("_pw")) colName+" VARCHAR(48)" else colName+" VARCHAR(20)")  + notNullAppender()
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy