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

.jwt-play-legacy_2.11.0.7.1.source-code.JwtSession.scala Maven / Gradle / Ivy

The newest version!
package pdi.jwt

import play.api.Play
import play.api.libs.json._
import play.api.libs.json.Json.JsValueWrapper
import pdi.jwt.algorithms.JwtHmacAlgorithm

/** Similar to the default Play Session but using JsObject instead of Map[String, String]. The data is separated into two attributes:
  * `headerData` and `claimData`. There is also a optional signature. Most of the time, you should only care about the `claimData` which
  * stores the claim of the token containing the custom values you eventually put in it. That's why all methods of `JwtSession` (such as
  * add and removing values) only modifiy the `claimData`.
  *
  * To see a full list of samples, check the [[http://pauldijou.fr/jwt-scala/samples/jwt-play/ online documentation]].
  *
  * '''Warning''' Be aware that if you override the `claimData` (using `withClaim` for example), you might override some attributes that
  * were automatically put inside the claim such as the expiration of the token.
  *
  */
case class JwtSession(
  headerData: JsObject,
  claimData: JsObject,
  signature: String
) {
  /** Merge the `value` with `claimData` */
  def + (value: JsObject): JwtSession = this.copy(claimData = claimData.deepMerge(value))

  /** Add this (key, value) to `claimData` (existing key will be overriden) */
  def + (key: String, value: JsValueWrapper): JwtSession = this + Json.obj(key -> value)

  /** Convert `value` to its JSON counterpart and add it to `claimData` */
  def + [T](key: String, value: T)(implicit writer: Writes[T]): JwtSession = this + Json.obj(key -> writer.writes(value))

  /** Add a sequence of (key, value) to `claimData` */
  def ++ (fields: (String, JsValueWrapper)*): JwtSession = this + Json.obj(fields: _*)

  /** Remove one key from `claimData` */
  def - (fieldName: String): JwtSession = this.copy(claimData = claimData - fieldName)

  /** Remove a sequence of keys from `claimData` */
  def -- (fieldNames: String*): JwtSession = this.copy(claimData = fieldNames.foldLeft(claimData) {
    (data, fieldName) => (data - fieldName)
  })

  /** Retrieve the value corresponding to `fieldName` from `claimData` */
  def get(fieldName: String): Option[JsValue] = (claimData \ fieldName).toOption

  /** After retrieving the value, try to read it as T, if no value or fails, returns None. */
  def getAs[T](fieldName: String)(implicit reader: Reads[T]): Option[T] =
    get(fieldName).flatMap(value => reader.reads(value).asOpt)

  /** Alias of `get` */
  def apply(fieldName: String): Option[JsValue] = get(fieldName)

  def isEmpty(): Boolean = claimData.keys.isEmpty

  def claim: JwtClaim = jwtClaimReader.reads(claimData).get
  def header: JwtHeader = jwtHeaderReader.reads(headerData).get

  /** Encode the session as a JSON Web Token */
  def serialize: String = JwtSession.key match {
    case Some(k) => JwtJson.encode(headerData, claimData, k)
    case _ => JwtJson.encode(headerData, claimData)
  }

  /** Overrride the `claimData` */
  def withClaim(claim: JwtClaim): JwtSession = this.copy(claimData = JwtSession.asJsObject(claim))

  /** Override the `headerData` */
  def withHeader(header: JwtHeader): JwtSession = this.copy(headerData = JwtSession.asJsObject(header))

  /** Override the `signature` (seriously, you should never need this method) */
  def withSignature(signature: String): JwtSession = this.copy(signature = signature)

  /** If your Play app config has a `session.maxAge`, it will extend the expiration by that amount */
  def refresh(): JwtSession = JwtSession.MAX_AGE.map(sec => this + ("exp", JwtTime.nowSeconds + sec)).getOrElse(this)
}

object JwtSession {
  // This half-hack is to fix a "bug" in Play Framework where Play assign "null"
  // values to missing keys leading to ConfigException.Null in Typesafe Config
  // Especially strange for the maxAge key. Not having it should mean no session timeout,
  // not crash my app.
  def wrap[T](getter: String => Option[T]): String => Option[T] = key => try {
    getter(key)
  } catch {
    case e: com.typesafe.config.ConfigException.Null => None
    case e: java.lang.RuntimeException => {
      e.getCause() match {
        case _: com.typesafe.config.ConfigException.Null => None
        case _ => throw e
      }
    }
  }

  val getConfigString = wrap[String](
    key => Play.maybeApplication.flatMap(_.configuration.getString(key))
  )

  val getConfigMillis = wrap[Long](
    key => Play.maybeApplication.flatMap(_.configuration.getMilliseconds(key))
  )

  val HEADER_NAME: String = getConfigString("play.http.session.jwtName").getOrElse("Authorization")

  val MAX_AGE: Option[Long] = getConfigMillis("play.http.session.maxAge").map(_ / 1000)

  val ALGORITHM: JwtHmacAlgorithm =
    getConfigString("play.http.session.algorithm")
      .map(JwtAlgorithm.fromString)
      .flatMap {
        case algo: JwtHmacAlgorithm => Option(algo)
        case _ => throw new RuntimeException("You can only use HMAC algorithms for [play.http.session.algorithm]")
      }
      .getOrElse(JwtAlgorithm.HmacSHA256)

  val TOKEN_PREFIX: String = getConfigString("play.http.session.tokenPrefix").getOrElse("Bearer ")

  private def key: Option[String] = getConfigString("play.crypto.secret")

  def deserialize(token: String, options: JwtOptions): JwtSession = (key match {
      case Some(k) => JwtJson.decodeJsonAll(token, k, Seq(ALGORITHM), options)
      case _ => JwtJson.decodeJsonAll(token, options)
    }).map { tuple =>
      JwtSession(tuple._1, tuple._2, tuple._3)
    }.getOrElse(JwtSession())

  def deserialize(token: String): JwtSession = deserialize(token, JwtOptions.DEFAULT)

  private def asJsObject[A](value: A)(implicit writer: Writes[A]): JsObject = writer.writes(value) match {
    case value: JsObject => value
    case _ => Json.obj()
  }

  def defaultHeader: JwtHeader = key.map(_ => JwtHeader(ALGORITHM)).getOrElse(JwtHeader())

  def defaultClaim: JwtClaim = MAX_AGE match {
    case Some(seconds) => JwtClaim().expiresIn(seconds)
    case _ => JwtClaim()
  }

  def apply(jsClaim: JsObject): JwtSession =
    JwtSession.apply(asJsObject(defaultHeader), jsClaim)

  def apply(fields: (String, JsValueWrapper)*): JwtSession =
    if (fields.isEmpty) {
      JwtSession.apply(defaultHeader, defaultClaim)
    } else {
      JwtSession.apply(Json.obj(fields: _*))
    }

  def apply(jsHeader: JsObject, jsClaim: JsObject): JwtSession =
    new JwtSession(jsHeader, jsClaim, "")

  def apply(claim: JwtClaim): JwtSession =
    JwtSession.apply(defaultHeader, claim)

  def apply(header: JwtHeader, claim: JwtClaim): JwtSession =
    new JwtSession(asJsObject(header), asJsObject(claim), "")

  def apply(header: JwtHeader, claim: JwtClaim, signature: String): JwtSession =
    new JwtSession(asJsObject(header), asJsObject(claim), signature)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy