Play 2.4 disable certain filters set based on request path or method
Asked Answered
T

4

8

In Play 2.3, I can disable certain filters based on different request path or method. However, I can't find a way to do it in Play 2.4. https://www.playframework.com/documentation/2.4.x/ScalaHttpFilters. How can I achieve similar result in Play 2.4 HttpFilters.

Here is how I did it in Play 2.3.

object CacheCtrlHeadersFilter extends EssentialFilter {
  def apply(action: EssentialAction) = new EssentialAction {
    def apply(requestHeader: RequestHeader) = {
      action(requestHeader).map { result =>
        result.withHeaders(
            CACHE_CONTROL -> "no-cache, no-store, must-revalidate, private",
            PRAGMA -> "no-cache"
          )
      }
    }
  }
}

import play.api.libs.iteratee._
object FilterChainByRequestHeader {
  def apply[A](action: EssentialAction, filtersFun: (RequestHeader) => List[EssentialFilter]): EssentialAction = new EssentialAction {
    def apply(rh: RequestHeader): Iteratee[Array[Byte], Result] = {
      val chain = filtersFun(rh).reverse.foldLeft(action) { (a, i) => i(a) }
      chain(rh)
    }
  }
}

object Global extends GlobalSettings {

  val securityFilter = SecurityHeadersFilter()
  val defaultFilters = List(securityFilter,
                            CacheCtrlHeadersFilter)

  def filters(rh: RequestHeader) = {
    if (rh.method == "OPTIONS")                       <----------- by method
      defaultFilters.filterNot(_.eq(securityFilter))
    else if (rh.path.startsWith("/apps/google"))      <----------- by path
      defaultFilters.filterNot(_.eq(securityFilter))
    else defaultFilters
  }
  override def doFilter(a: EssentialAction): EssentialAction = {
    FilterChainByRequestHeader(super.doFilter(a), filters)
  }

}

There is no RequestHeader available in HttpFilters in Play 2.4

class Filters @Inject() (
  securityHeadersFilter: SecurityHeadersFilter,
  cacheCtrlHeadersFilter: CacheCtrlHeadersFilter
) extends HttpFilters {

  val filters = Seq(securityHeadersFilter, cacheCtrlHeadersFilter)
}
Triste answered 23/12, 2015 at 7:57 Comment(4)
Just a thought, but maybe you can extend the filter and build the logic in there?Foy
@Foy Yes, I thought about that. But I wonder if this is the only solution.Triste
@Foy Also, if I build a filter, how can I skip the current filter inside the apply function?Triste
I don't have the Scala docs in front of me right now (and am not fluent in Scala), but I'd imagine an if statement to only call the super / alter the result if true and not altering / returning the result unaltered in the elseFoy
T
2

This is what I ended up doing.

class SecurityHeadersFilter extends EssentialFilter {
  def apply(action: EssentialAction) = new EssentialAction {
    def apply(rh: RequestHeader) = {
      action(rh).map { result =>
        if (rh.method != "OPTIONS" && !rh.path.startsWith("/apps/google"))
          result.withHeaders(
              "X-Frame-Options" -> "SAMEORIGIN",
              "Content-Security-Policy" -> "default-src * 'self' 'unsafe-inline' 'unsafe-eval' "
            )
      }
    }
  }
}

class Filters @Inject() (
  gzip: GzipFilter,
  cache: CacheCtrlHeadersFilter,
  cors: CorsFilter,
  security: SecurityHeadersFilter
) extends HttpFilters {
  val filters = Seq(gzip, cache, cors, security)
}
Triste answered 1/1, 2016 at 2:23 Comment(0)
E
3
object Global extends WithFilters(new CoreActionFilter()) {

}


class CoreActionFilter extends Filter {

  lazy val serviceLogger = Logger("services")

  def apply(nextFilter: RequestHeader => Future[Result])(requestHeader: RequestHeader) = {

    nextFilter(requestHeader).map { result =>

      val action = requestHeader.path match {
        case "/favicon.ico" => s"[ ${requestHeader.remoteAddress.toString}]"
        case _ => s"[ ${requestHeader.remoteAddress.toString}]" + ("[") + requestHeader.tags(Tags.RoutePattern) + ("]") + ("[") + requestHeader.tags(Tags.RouteController) + "." + requestHeader.tags(Tags.RouteActionMethod) + ("]")
      }

      val startTime = System.currentTimeMillis
      val endTime = System.currentTimeMillis
      val requestTime = endTime - startTime
      val token = requestHeader.headers.get("X-Auth-Token")
      serviceLogger.info(s"[$token]" + s"${action}[${requestTime}ms]" + s"[${result.header.status}]")
      result.withHeaders("Request-Time" -> requestTime.toString)

    }(CoreContexts.simpleSqlQuery)

  }
}
Empathy answered 29/12, 2015 at 16:24 Comment(2)
Can you please explain your answer? How can I skip a filter in the method your provided?Triste
Please see my implementation: In my project, it also has a filters. I put the condition validation in my filter. For example: CoreActionFilter . you could try implement the apply method to make the condition validation.Empathy
E
2

My implementation:

class Filters @Inject() (corsFilter: CORSFilter,coreActionFilter: CoreActionFilter) extends HttpFilters { def filters = Seq(corsFilter, coreActionFilter) }

class CoreActionFilter extends Filter { def apply(nextFilter: RequestHeader => Future[Result])(requestHeader: RequestHeader) = {

if(/*yourContidion*/) {

  nextFilter(requestHeader).map { result =>

    val action = requestHeader.path match {
      case "/favicon.ico" => s"[ ${requestHeader.remoteAddress.toString}]"
      case _ => s"[ ${requestHeader.remoteAddress.toString}]" + ("[") + requestHeader.tags(Tags.RoutePattern) + ("]") + ("[") + requestHeader.tags(Tags.RouteController) + "." + requestHeader.tags(Tags.RouteActionMethod) + ("]")
    }

    val startTime = System.currentTimeMillis
    val endTime = System.currentTimeMillis
    val requestTime = endTime - startTime
    val token = requestHeader.headers.get("X-Auth-Token")
    serviceLogger.info(s"[$token]" + s"${action}[${requestTime}ms]" + s"[${result.header.status}]")
    result.withHeaders("Request-Time" -> requestTime.toString)

  }(CoreContexts.simpleSqlQuery)
}
nextFilter(requestHeader)

} }

Empathy answered 30/12, 2015 at 1:37 Comment(1)
Please edit your previous answer when you want to add something, instead of posting a new answerFoy
T
2

This is what I ended up doing.

class SecurityHeadersFilter extends EssentialFilter {
  def apply(action: EssentialAction) = new EssentialAction {
    def apply(rh: RequestHeader) = {
      action(rh).map { result =>
        if (rh.method != "OPTIONS" && !rh.path.startsWith("/apps/google"))
          result.withHeaders(
              "X-Frame-Options" -> "SAMEORIGIN",
              "Content-Security-Policy" -> "default-src * 'self' 'unsafe-inline' 'unsafe-eval' "
            )
      }
    }
  }
}

class Filters @Inject() (
  gzip: GzipFilter,
  cache: CacheCtrlHeadersFilter,
  cors: CorsFilter,
  security: SecurityHeadersFilter
) extends HttpFilters {
  val filters = Seq(gzip, cache, cors, security)
}
Triste answered 1/1, 2016 at 2:23 Comment(0)
C
1

This is how I implemented. It's working, but not ideal. Basically we add an outer most WhiteListFilter, to enrich the request, so that the request will never be filtered out.

If the filter is about manipulating response, then the same filter can be used to unset the response effectively.

Disclaimer, I typed following code with a text editor just for you to get a picture. Cannot guarantee it compiles:

object WhiteListFilter extends EssentialFilter {

  override def apply(next: EssentialAction) = new EssentialAction {
    override def apply(req: RequestHeader) = {

      val whiteListedReq =
        // if the filter is about to block some request by headers
        // then append the valid headers, to `whitelist` request
        if (req.method == "OPTIONS") {
          req.copy(headers = req.headers.add("XXX" -> "XXX"))
        } else {
          req
        }

      next(whiteListedReq).map {
        resp =>
          // if the filter is about to manipulate response
          // then unset the actions
          if (req.method == "OPTIONS") {
            val headers = resp.headers - "key1" - "key2"
            resp.copy(headers = headers)
          } else {
            resp
          }
      }
    }
  }
}
Commemorative answered 1/1, 2016 at 0:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.