Defer application of Req until after multiple steps of authentication via dispatch handlers

30 views
Skip to first unread message

David Hagan

unread,
Jul 14, 2016, 9:16:06 PM7/14/16
to Lift
Hi all,

Can you please provide advice for how to use the webapp's full lift pipeline, to convert a Req into a LiftResonse from within a dispatch partial function?

In detail:

I'm inserting an authentication framework into my application, and I'm using the dispatch tables to intercept calls to my application when the user is not logged in.  When I intercept, I want to store the Req that they were trying to make, and once authentication is complete, apply that Req in response to a later call within that session.  What's the right entry point from within a running webapp, to turn an unused Req into a LiftResponse, using the full lift pipeline?

Example code (not checked for compilation - just writing this directly into this post) to explain the context of my question:

def loggedIn:Boolean // this would determine whether the user is already logged in
def testLoginPage(r:Req):Boolean //this would check the credentials of the user


def applyStoredReq(storedReq:Req):Box[LiftResponse] = {
 
??? // this is the bit I'd like some assistance with.  I'd like to know that any logic I have in sitemap, statelessdispatch and dispatch are all applied.
}


val
LoginFormPageLocPrefix = "loginForm"
def LoginFormPageLoc(originalRequestId:String) = List(LoginFormPageLocPrefix,originalRequestId)
object reqStore extends SessionVar[Map[String,Req]](Map.empty[String,Req])

LiftRules.dispatch.prepend {
 
case req:Req if !loggedIn => () => {
    val originalReqId
= nextFuncName
    reqStore
(reqStore.is.updated(originalReqId,req))    
   
Full(RedirectResponse(LoginFormPageLoc(originalReqId)))
 
}
 
case Req(List(LoginFormPageLoc,originalReqId),_,_) => () => {
   
// this would generate a NodeResponse containing an html form which redirects to /handleLoginForm?originalReqId={originalReqId}, into which the user would authenticate
 
}
 
case r@Req("handleLoginForm") => () => {
    testLoginPage
(r) match {
     
case true => {
       
for (
          originalReqId
<- r.param("originalReqId");
          originalReq
<- reqStore.is.get(originalReqId);
          resp
<- applyStoredReq(originalReq)
       
) yield resp
     
}
     
case _ => r.param("originalReqId").map(originalReqId => RedirectResponse(LoginFormPageLoc(originalReqId)))
   
}
 
}
}

I'm hoping to store the Req so that I can handle the original request, regardless of its HTTP method and payload.  For the purpose of this example, I'm demonstrating a form-based interception, but in actuality, this is going to be wired into a more complex multi-provider delegated authentication system (SAML, openIdConnect and LTI based authentication).  I'm just not sure how to apply the stored Req.

Thankyou.

Diego Medina

unread,
Jul 14, 2016, 9:55:09 PM7/14/16
to Lift
I run into somewhat of a similar situation a few months ago and the  way I solved it was by writing a custom serlet filter.

The code I'm using is here

This is our use case and hopefully it will work for you too.

We have two jetty context on our app, one is served by Lift with all the usual auth code, then we have a /docs context, which is just plain html files for documentation, we didn't want to include these files as part of the war file, so we decided to host them separately, but because of the nature of our app, those docs cannot be accessed by the public, unless the user is logged in into the main app.

What the filter does is, it checks the user's cookies, looks for a specific one that tells our app the user is logged in (we use mongo auth for auth so we look for MongoAuth.extSessionCookieName), if it is not found, then we simply redirect the user to the normal login page, our login page stores the referer url and after login, it is sent back to the docs page.

If the cookie is found and after checking in the db, the value is valid and current, we let the chain of requests go and the user can see the docs.

I think this can work for you too, just implement whatever logic your auth framework uses to validate a request in the filter, and then you don't have to mess with each Req manually.

Hope this helps and if you need any more info, let me know.

I'm pasting the gist below in case anyone searches for keywords found there

Thanks

Diego

package code.servlets

import java.util.UUID
import javax.servlet._
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}

import code.config.AppSettings
import code.model.AcmApp
import code.model.user.ExtSession._
import net.liftmodules.mongoauth.MongoAuth
import net.liftweb.common.{Empty, Box, Full, Failure}
import net.liftweb.util.Helpers._

class LoggedInFilter extends Filter {

  def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
    val httpReq = req.asInstanceOf[HttpServletRequest]
    val sessionId = httpReq.getSession.getId
    val extSess = for {
      cookies <- Box.legacyNullTest(httpReq.getCookies)
      cookie <- Box(cookies.find { c => c.getName == MongoAuth.extSessionCookieName.vend })
      cookieValue = cookie.getValue
      uuid <- tryo(UUID.fromString(cookieValue)) ?~ "Invalid UUID"
      es <- find(uuid) ?~ "ExtSession not found: %s".format(uuid.toString)
    } yield {
      es
    }
    val resp = res.asInstanceOf[HttpServletResponse]

    extSess match {
      case Failure(msg, _, _) =>
        deleteExtCookie()
        resp.sendRedirect(AppSettings.protocol + "://" + AcmApp.Dashboard.domain + "/login")
      case Full(es) if es.expires.isExpired => // if it's expired, delete it and the cookie
        deleteExtCookie()
        resp.sendRedirect(AppSettings.protocol + "://" + AcmApp.Dashboard.domain + "/login")
      case Empty =>
        resp.sendRedirect(AppSettings.protocol + "://" + AcmApp.Dashboard.domain + "/login")
      case _ =>
        chain.doFilter(req, res)
    }

  }

  def init(config: FilterConfig): Unit = {}

  def destroy(): Unit = {}

}
=================

Thanks













--
--
Lift, the simply functional web framework: http://liftweb.net
Code: http://github.com/lift
Discussion: http://groups.google.com/group/liftweb
Stuck? Help us help you: https://www.assembla.com/wiki/show/liftweb/Posting_example_code

---
You received this message because you are subscribed to the Google Groups "Lift" group.
To unsubscribe from this group and stop receiving emails from it, send an email to liftweb+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Diego Medina
Lift/Scala Consultant
di...@fmpwizard.com
http://blog.fmpwizard.com/

Antonio Salazar Cardozo

unread,
Jul 31, 2016, 7:15:38 PM7/31/16
to Lift
A random thought that just occurred to me, with zero attempts at
actually implementing it… I wonder if you could intercept this and
then wrap the original request's template in a comet… This would
let the original request be processed normally (more or less?),
and you would then render that comet by name once the user is
properly authenticated.

Bit far-fetched, and might be an absolutely terrible idea, but figured
I'd mention it since it just crossed my mind.
Thanks,
Antonio

David Hagan

unread,
Jul 31, 2016, 7:58:31 PM7/31/16
to Lift
Ooh, that's interesting.  If it's the request's template though, wouldn't that be more useful for snippet rendering rather than for those endpoints which are handled by REST helpers on the dispatch table?  Maybe I'm misunderstanding request templates?

I've gone with a solution which begins from Diego's suggestion, and then builds on that with a copying of all the appropriate part of the HttpServletRequest, saving them into an object which is stored in the user's session, and that allows me to swap in the stored request into the chain.doFilter call.  That does mean that I'm operating at one filter up the chain, so I'd still love to see an entry point down the line within lift which allows a Req to be processed through the entire LiftEngine, but for the moment it's resolving my issue.  The sticky points were freezing and duplicating the HttpServletRequest, and also the decision logic around which stored HttpServletRequest to replay.

On the downside, I have a nasty feeling that I'm largely reimplementing Apache Shiro, and that I probably should've started with that and tweaked slightly instead of implementing from scratch, and perhaps I'll migrate to that at some point.

My final solution's logic looks vaguely like:

package code.servlets
import javax.servlet._
import javax.servlet.http.{HttpServletRequest, HttpServletResponse, HttpSession}

class LoggedInFilter extends Filter {
 
def duplicateAndFreezeRequest(req: HttpServletRequest):HttpServletRequest = ??? // because servlet request is a lightweight reference to the current request, it needs to have its values copied to an instance which isn't related to the container, but which still returns the container's req from { def getRequest }, so I used a Lift MockHttpServletRequest and populated it with values from the original request.

 
def storeRequest(session:HttpSession,req:HttpServletRequest):Unit = ??? // because there are so many asynchronous calls flying around from cometActors, it isn't necessarily true that the first request on a new session will be the one which represents the navigate call of the user's browser, so the datamodel can't be as simple as a one-to-one relationship between user sessions and stored requests. So, storing, clearing and retrieving all need to be able to use the current HttpServletRequest as a key to determine which stored request to resume.
  def clearRequest(session:HttpSession,req:HttpServletRequest):Unit = ???
 
def retrieveRequest(session:HttpSession,req:HttpServletRequest):Option[HttpServletRequest] = ???
 
def isAuthenticated(session:HttpSession,req:HttpServletRequest):Boolean = ??? // for the sake of this example, I'm simplifying all the question of how authentication occurs from the mechanism of storing and replaying the request(s)
  def doAuthentication(session:HttpSession,req:HttpServletRequest,resp:HttpServletResponse):Unit = ???

 
 
def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
    val httpReq
= req.asInstanceOf[HttpServletRequest]

    val session
= httpReq.getSession
    val resp
= res.asInstanceOf[HttpServletResponse]
    isAuthenticated match
{
     
case true => {
        retrieveRequest
(session,httpReq).map(originalRequest => {
          clearRequest
(session,httpReq)
          chain
.doFilter(originalRequest,resp)
        }).getOrElse({
          chain
.doFilter(httpReq,resp)
        })
     
}
     
case false => {
        doAuthentication
(session,httpReq,resp)

      }
    }
  }
 
def init(config: FilterConfig): Unit = {}
 
def destroy(): Unit = {}
}


Thanks again to both of you.

Of course, it still interests me to know whether there's an entry point from within a LiftApp where I can process a Req against the entire Lift Engine, to return a LiftResponse, and know that behaviour in statelessDispatch, dispatch and snippets all had their normal opportunity to handle it.  If you've any further thoughts about that entrypoint, I'd love to hear them.

-Dave

Antonio Salazar Cardozo

unread,
Aug 1, 2016, 9:34:54 AM8/1/16
to Lift
REST helpers would indeed be different, but could perhaps
be handled similarly via async rest stuff. That would be more
complicated though. Something to marinate on for sure—it'd
be fun to discover that this was already possible with what's
there.

I'll see if I can come up with anything, but I'm not sure I will :)
Thanks,
Antonio
Reply all
Reply to author
Forward
0 new messages