lift/framework

Run request matching once

waern opened this issue · 1 comments

waern commented

Hi,

I have a client that does side-effecting request matching. Simplified example:

object AuthRest extends RestHelper {
  serve {
    case r@Req("api" :: _, _, _) if !isAuthenticated(r) =>
        ForbiddenResponse("requiresUserLogin")
  }
}

This leads to a race-condition due to how request matching in Lift works. Here is the relevant Lift code:

https://github.com/lift/framework/blob/master/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala#L466

This Lift logic firsts calls isDefinedAt (indirectly) to search for a match, triggering a call to isAuthenticated which can return false, which makes the guard succeed and results in a match. Then once the match has been found, isDefinedAt is called again (indirectly), here:

https://github.com/lift/framework/blob/master/web/webkit/src/main/scala/net/liftweb/http/LiftServlet.scala#L484

This triggers another call to isAuthenticated. But since isAuthenticated is an effectful function, it can now return true, causing an exception to be thrown from get in this line of code:

https://github.com/lift/framework/blob/master/web/webkit/src/main/scala/net/liftweb/http/rest/RestHelper.scala#L573

I think this could be fixed by simply using the lift method instead of isDefinedAt from PartialFunction to match and remember the "continuation" in one go, and then apply the continuation instead of the original partial function to avoid pattern-matching twice.

This is working as intended. Deeper explanation in the mailing list thread, along with suggestions on how to adjust.