app/com/gu/viewer/proxy/PreviewProxy.scala (105 lines of code) (raw):
package com.gu.viewer.proxy
import com.gu.viewer.config.AppConfig
import com.gu.viewer.controllers.routes
import com.gu.viewer.logging.Loggable
import play.api.mvc.Result
import scala.concurrent.{ExecutionContext, Future}
class PreviewProxy(proxyClient: ProxyClient, config: AppConfig)(implicit ec: ExecutionContext) extends Loggable {
private val PREVIEW_AUTH_REDIRECT_PARAM = "redirect-url"
val serviceHost = config.previewHost
val previewLoginUrl = s"https://$serviceHost/login"
private def loginCallbackUrl(request: PreviewProxyRequest) =
s"https://${request.requestHost}${routes.Proxy.previewAuthCallback}"
/**
* Transform proxy server relative URI to viewer URI.
*/
private def proxyUriToViewerUri(uri: String) = {
val proxyUri = """^\/proxy(\/.+)$""".r
uri match {
case proxyUri(path) => Some(path)
case _ => None
}
}
private def doPreviewAuth(request: PreviewProxyRequest) = {
val proxyRequestUrl = previewLoginUrl
log.info(s"Proxy Preview auth to: $proxyRequestUrl")
def handleResponse(response: ProxyResponse) = {
val returnUrl = proxyUriToViewerUri(request.requestUri).getOrElse(request.requestUri)
// Store new preview session from response
// - should we also remove auth cookie from session to ensure its recreated?
val session = PreviewSession.fromResponseHeaders(response)
.withPlaySessionFrom(request.session)
.withReturnUrl(Some(returnUrl))
val locHeader = response.header("Location")
(session.sessionCookie, locHeader) match {
case (Some(_), Some(location)) => Future.successful {
PreviewAuthRedirectProxyResult(location, session)
}
case (None, _) => error("Unexpected response session from preview login request", response)
case (_, None) => error("Invalid response from preview login request", response)
}
}
proxyClient.post(proxyRequestUrl, queryString = Seq(PREVIEW_AUTH_REDIRECT_PARAM -> loginCallbackUrl(request))) {
case response if response.status == 303 => handleResponse(response)
case response => error("Unexpected response from preview authentication request", response)
}
}
private def doPreviewProxy(request: PreviewProxyRequest) = {
val url = s"https://$serviceHost/${request.servicePath}"
log.info(s"Proxy GET to preview: $url")
def isLoginRedirect(response: ProxyResponse) = {
response.status == 303 && response.header("Location").isDefined
}
val cookies = request.session.asCookies ++ request.maybePandaCookieToForward.toSeq
proxyClient.get(url, cookies = cookies) {
case response if isLoginRedirect(response) => doPreviewAuth(request)
}
}
private def doPreviewProxyPost(request: PreviewProxyRequest) = {
val url = s"https://$serviceHost/${request.servicePath}"
log.info(s"Proxy POST to preview: $url")
def isLoginRedirect(response: ProxyResponse) = {
response.status == 303 &&
response.header("Location").exists(l => l == previewLoginUrl || l == "/login")
}
val cookies = request.session.asCookies
proxyClient.post(url, cookies = cookies, body = request.body.getOrElse(Map.empty)) {
case response if isLoginRedirect(response) => doPreviewAuth(request)
}
}
/**
* Entry-point for proxying a request to preview
*/
def proxy(request: PreviewProxyRequest): Future[Result] = ProxyResult.resultFrom {
log.info(s"Received proxy request for: ${request.requestUri}")
doPreviewProxy(request)
}
def proxyPost(request: PreviewProxyRequest): Future[Result] = ProxyResult.resultFrom {
log.info(s"Received proxy POST request for: ${request.requestUri}")
doPreviewProxyPost(request)
}
/**
* Preview Authentication callback.
*
* Proxy all request params and Preview session cookie to Preview authentication callback.
* Store response cookies into Viewer's play session.
*/
def previewAuthCallback(request: PreviewProxyRequest): Future[Result] = ProxyResult.resultFrom {
val redirectUrlParam = PREVIEW_AUTH_REDIRECT_PARAM -> loginCallbackUrl(request)
val queryParams = request.requestQueryString.view.mapValues(_.head).toSeq :+ redirectUrlParam
def handleResponse(response: ProxyResponse) = {
val session = PreviewSession.fromResponseHeaders(response)
.withPlaySessionFrom(request.session)
(session.sessionCookie, session.authCookie) match {
case (Some(_), Some(_)) => Future.successful {
val returnUrl = request.session.returnUrl.getOrElse("/proxy/preview/uk")
RedirectProxyResultWithSession(returnUrl, session.withoutReturnUrl)
}
case (None, None) => error("Bad response from preview auth callback", response)
case (None, _) => error("Preview Session cookie not returned", response)
case (_, None) => error("Preview Auth cookie not returned", response)
}
}
request.session.sessionCookie match {
case None => error("Preview session not established")
case Some(_) => {
val proxyUrl = s"http://${config.previewHost}/oauth2callback"
log.info(s"Proxy preview auth callback to: $proxyUrl")
val cookies = request.session.asCookies
proxyClient.get(proxyUrl, queryString = queryParams, cookies = cookies) {
case r => handleResponse(r)
}
}
}
}
private def error(msg: String) =
Future.failed(ProxyError(msg, None))
private def error(msg: String, response: ProxyResponse) =
Future.failed(ProxyError(msg, Some(response)))
}