EssentialAction: How to Get the Body of a Request without Parsing It - scala

Given the following EssentialAction...
object MyController extends Controller {
...
def HasToken(action: Token => EssentialAction) = EssentialAction { request =>
...
// this doesn't compile
val body = request.body match {
case json: JsValue => json.toString
case _ => ""
}
// calculate hash with body content here
...
}
// here is an authenticated action
def getUser(userId: Strign) = HasToken { token =>
Action(parse.json) { request =>
request.body.validate[User] match {
...
}
}
}
}
... how do I get the body of the request without parsing it?
I don't want and I don't need to parse the request body in HasToken since the body is going to be parsed in action getUser. I just need the raw content of the body to calculate a hash.
The code in HasToken doesn't compile because request is of type RequestHeader whereas I need a Request, which defines body.

Will this work for you ?
object MyController extends Controller {
// Your HasToken Action
def Authenticate(action: Token => EssentialAction) = EssentialAction { requestHeader =>
// ... execute logic to verify authenticity using requestHeader
}
// Your action to validate tampering of request body and validity of JSON
def Validate[A](action: Token => Request[A]) = Action(parse.json) { request =>
val body = request.body
body match {
case json: JsValue => json.toString
case _ => ""
}
// calculate hash with body content here
body.validate[User] match {
// ...
}
}
def getUser(userId: Strign) = Authenticate { token =>
Validate { user =>
//.... Continue
}
}
}
Authentication only uses RequestHeader
Validation uses Request body. (Bonus: Body is only parsed once)
EDIT:
Question #1: I don't want to validate the body in Validate... since I need a generic validation mechanism that could be used everywhere regardless of the content type (e.g. user, message, etc.).
How about adding another type param (so that it is made generic):
def Validate[A, B](action: Token => Request[A])(implicit reads: Reads[B]) = Action(parse.json) { request =>
// ...
}
Question #2: Furthermore, if the token validation fails, the body don't have to be processed (that's important in case of file upload, which has to be performed if and only if the validation succeeded). That's way, in my opinion, the best option would be to read the raw content of the body in Validate.
This can be easily achieved:
def Validate[A, B](action: Token => Request[A])(implicit reads: Reads[B]) = Action(parse.json) { request =>
val body = request.body
body match {
case json: JsValue => json.toString
case _ => ""
}
// calculate hash with body content here and figure out if the body is tampered
if (bodyIsNotTampered) {
body.validate[B] match {
// ...
}
} else {
// log and return Future.successful(BadRequest)
}
}
EDIT 3: Full solution:
import play.api.libs.json.{Json, JsValue, Format}
object CompilationUtils {
class Token
case class User(name: String)
implicit val UserFormat = Json.format[User]
def authenticate = new Token // authentication logic
def isTampered(body: JsValue) = {
val bodyAsStr: String = Json.stringify(body)
// calculate hash with body content here
false
}
}
object MyController extends Controller {
import CompilationUtils._
// Your HasToken Action
def Authenticate(action: Token => EssentialAction) = EssentialAction { requestHeader =>
action(authenticate)(requestHeader) // your execute logic to verify authenticity using requestHeader
}
// Your action to validate tampering of request body and validity of JSON
def Validate[A, B](request: Request[A])(implicit formatA: Format[A], formatB: Format[B]): Either[Result, B] = {
val body = request.body
val bodyAsJsValue = Json.toJson(body)
if (!isTampered(bodyAsJsValue)) {
bodyAsJsValue.validate[B].fold(
valid = res => Right(res),
invalid = err => Left(BadRequest(err.toString))
)
} else {
Left(BadRequest) // Request Tampered
}
}
def getUser(userId: String) = Authenticate { token =>
Action(parse.json) { request =>
Validate(request).fold(
badReq => badReq,
user =>
// continue...
Ok("")
)
}
}
}

Related

Akka-http logrequest not logging the request body

I am using akka-http and trying to log a request on a specific path using logrequest :
path(Segment / "account") { id =>
logRequest("users/account", Logging.InfoLevel) {
post {
entity(as[Account]) { account => ???
complete(HttpResponse(StatusCodes.NoContent))
}
}
}
however on my log I see something like
HttpRequest(HttpMethod(POST),https://localhost:9009/api/users/123/account,List(Host: localhost:9009, User-Agent: akka-http/10.0.6, Timeout-Access: <function1>),HttpEntity.Chunked(application/json),HttpProtocol(HTTP/1.1))
what I am looking for is the exact request including the body (json) as it was sent by the requestor.
The "HttpEntity.Chunked(application/json)" segment of the log is the output of HttpEntity.Chunked#toString. To get the entire request body, which is implemented as a stream, you need to call HttpEntity#toStrict to convert the Chunked request entity into a Strict request entity. You can make this call in a custom route:
def logRequestEntity(route: Route, level: LogLevel)
(implicit m: Materializer, ex: ExecutionContext) = {
def requestEntityLoggingFunction(loggingAdapter: LoggingAdapter)(req: HttpRequest): Unit = {
val timeout = 900.millis
val bodyAsBytes: Future[ByteString] = req.entity.toStrict(timeout).map(_.data)
val bodyAsString: Future[String] = bodyAsBytes.map(_.utf8String)
bodyAsString.onComplete {
case Success(body) =>
val logMsg = s"$req\nRequest body: $body"
loggingAdapter.log(level, logMsg)
case Failure(t) =>
val logMsg = s"Failed to get the body for: $req"
loggingAdapter.error(t, logMsg)
}
}
DebuggingDirectives.logRequest(LoggingMagnet(requestEntityLoggingFunction(_)))(route)
}
To use the above, pass your route to it:
val loggedRoute = logRequestEntity(route, Logging.InfoLevel)

Play Framework: How to Add a Header to Every Response

In the following Controller, Authenticated extracts the token from the request headers and invokes a given action if and only if the token is valid (the code has been simplified for clarity):
object MyController extends Controller {
def Authenticated(action: Token => EssentialAction) = EssentialAction { requestHeader =>
val jwt = requestHeader.headers.get(HeaderNames.AUTHORIZATION) match {
case Some(header) => s"""$AuthScheme (.*)""".r.unapplySeq(header).map(_.head.trim)
case _ => requestHeader.getQueryString("auth").map(UriEncoding.decodePath(_, SC.US_ASCII.name))
}
jwt match {
case Some(t) if t.isValid =>
val token: Token = authService.token(t)
action(token)(requestHeader)
case _ => Done(Unauthorized.withHeaders(HeaderNames.WWW_AUTHENTICATE -> AuthScheme))
}
}
def getUser(userId: String) = Authenticated { token =>
Action.async { request =>
userService.find(userId).map {
case Some(user) => Ok(Json.obj("user" -> user.asJson)).withHeaders(
"token" -> authService.renew(token).asJson.toString
)
case _ => NotFound
}
}
}
}
The token returned by authService.token(t) is a JWT (JSON Web Token) and it can be used only once... so I need to return a new token after each request. The idea would be to put the new token in the response headers. That said, is there a way to add the token header to every response without having to invoke withHeader in each action?
Simply you can create a Filter and in Global.scala add WithFilters class.
import play.api.mvc._
object Global extends WithFilters(TokenFilter) {
...
}
Here is a Filter sample for logging so you could change it easily to satisfy your needs.
val loggingFilter = Filter { (next, rh) =>
val start = System.currentTimeMillis
def logTime(result: PlainResult): Result = {
val time = System.currentTimeMillis - start
Logger.info(s"${rh.method} ${rh.uri} took ${time}ms and returned ${result.header.status}")
result.withHeaders("Request-Time" -> time.toString)
}
next(rh) match {
case plain: PlainResult => logTime(plain)
case async: AsyncResult => async.transform(logTime)
}
}
I'd use ActionComposition. In Java it could look like:
public class YourActionComposition extends Action<YourAnnotation> {
#With(YourActionComposition.class)
#Target({ ElementType.TYPE, ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
public #interface YourAnnotation {
}
public F.Promise<Result> call(Http.Context ctx) throws Throwable {
Promise<Result> call = delegate.call(ctx);
// Add something to your headers here
return call;
}
}

Play framework 2.3.x OAuth & Actors "Cannot find HTTP Request Header here"

I'm using the example code from Play's ScalaOAuth documentation to authenticate to the twitter API. My full code is here:
object Twitter extends Controller {
val KEY = ConsumerKey("redacted")
val TWITTER = OAuth(ServiceInfo(
"https://api.twitter.com/oauth/request_token",
"https://api.twitter.com/oauth/access_token",
"https://api.twitter.com/oauth/authorize", KEY),
true)
def authenticate = Action { request =>
request.getQueryString("oauth_verifier").map { verifier =>
val tokenPair = sessionTokenPair(request).get
// We got the verifier; now get the access token, store it and back to index
TWITTER.retrieveAccessToken(tokenPair, verifier) match {
case Right(t) => {
// We received the authorized tokens in the OAuth object - store it before we proceed
Redirect(routes.Application.timeline).withSession("token" -> t.token, "secret" -> t.secret)
}
case Left(e) => throw e
}
}.getOrElse(
TWITTER.retrieveRequestToken("http://localhost:9000/auth") match {
case Right(t) => {
// We received the unauthorized tokens in the OAuth object - store it before we proceed
Redirect(TWITTER.redirectUrl(t.token)).withSession("token" -> t.token, "secret" -> t.secret)
}
case Left(e) => throw e
})
}
def sessionTokenPair(implicit request: RequestHeader): Option[RequestToken] = {
for {
token <- request.session.get("token")
secret <- request.session.get("secret")
} yield {
RequestToken(token, secret)
}
}
}
I spin up an actor that I want to use to search the Twitter API. I also use an edited version of their controller in that documentation to do this:
class TwitterParse extends Actor {
private var buildURL: String = _
private val urlBoilerPlate = "https://api.twitter.com/1.1/users/search.json?q="
private val pageCount = "&page=1&count=3"
def receive = {
case rawName: (Long, String) => {
buildURL = urlBoilerPlate + rawName._2 + pageCount
Twitter.sessionTokenPair match {
case Some(credentials) => {
WS.url(buildURL)
.sign(OAuthCalculator(Twitter.KEY, credentials))
.get
.map(result => println(result.json))
}
}
}
When I compile I get: "Cannot find any HTTP Request Header here" on the Twitter.sessionTokenPair call. This probably has to do with the fact that in their docs they include a
Action.async { implicit request =>
before calling sessionTokenPair. How would I fix this so it works in the Actor?
Thanks in advance

How to set a cookie in ActionBuilder in Play Framework 2.2 / Scala?

To set a cookie you usually manipulate the result in an action like Ok().withCookies(…).
I created an AuthenticatedAction extends ActionBuilder[AuthenticatedRequest] and need to update the expiry date of a user's cookie by setting a new cookie with a new maxAge sometimes. I cannot figure out how to do this, because I can't find a way to manipulate the result.
Within the invokeBlock function I call block(new AuthenticatedRequest(identity, request)), which returns a Future[SimpleResult] and I cannot use withCookies() on a Future[SimpleResult].
Here's my custom AuthenticatedAction:
class AuthenticatedRequest[A](val identity: Identity, request: Request[A]) extends WrappedRequest[A](request)
object AuthenticatedAction extends ActionBuilder[AuthenticatedRequest] {
def redirectToLogin = {
Redirect("/login")
}
def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) => Future[SimpleResult]) = {
request.cookies.get("mycookie").map { cookie =>
val maybeIdentity = Auth.validateAndTouchTokenAndGetUser(cookie.value)
maybeIdentity.map { identity =>
// If it's a persistent session, update timestamp by sending a new cookie sometimes!
// To simplify this example, assume we always want to set a new cookie.
val futureResult = block(new MaybeAuthenticatedRequest(maybeIdentity, request))
// What next?
val newMaxAge = 1234
// ???result???.withCookies(Cookie("mycookie", cookie.value, newMaxAge))
} getOrElse {
// Respond with redirect to login and delete cookie and a warning message
Future.successful(
redirectToLogin
.discardingCookies(DiscardingCookie("mycookie"))
.flashing("warning" -> "Your session has expired. Please sign in again.")
)
}
} getOrElse {
// Respond with redirect to login
Future.successful(redirectToLogin)
}
}
}
Ok is a SimpleResult as well. You cannot set cookies for a Future[SimpleResult] but you can do like this:
val futureResult: Future[SimpleResult] = ...
futureResult.map(_.withCookies(Cookie("mycookie", cookie.value, newMaxAge))
Update for Blankman:
The simpliest case for responding with cookies is like this:
def myAction = Action {
.....
Ok(response).withCookies(Cookie("cookie", cookieValue, maxAge))
}
This way you can compose your more complex Action like this (example from my project):
def safe(doSafe: Request[AnyContent] => Future[SimpleResult]): Action[AnyContent] = Action.async { implicit request =>
try {
doSafe(request).map(_.withCookies(Cookie("mycookie", cookie.value, newMaxAge))
} catch {
case e: Exception =>
e.printStackTrace()
//custom failure response here
}
}
Usage:
def someAction = safe { implicit request =>
.... //something that returns a Future[SimpleResult]
}

How to pass a variable to an Action from an intercepted request in PlayFramework?

I am intercepting all the requests to my play application by overriding onRouteRequest method of GlobalSettings. Now, I need to send some data to the despatched action from here so that I dont perform all those calculations in all the actions. How do I set an attribute to the request (play.api.mvc.RequestHeader) object that I pass to the super onRouteRequest method ?
For your need I don't thing using the onRouteRequest will work (elegantly at least).
But let's try to use a dedicated structure to intercept.
Here is how you could intercept the request, compute some generic stuff and pass it to Action
First of all, here is an Interceptor object that has a method intercept and a convenient method username :
object Interceptor {
def intercept[A, B](f: RequestHeader => Option[B], e: RequestHeader => Result)(action: B => Action[A]): Action[(Action[A], A)] = {
val bodyParser = BodyParser {
request =>
f(request) map {
b =>
val innerAction = action(b)
innerAction.parser(request).mapDone {
body => body.right.map(innerBody => (innerAction, innerBody))
}
} getOrElse {
Done(Left(e(request)), Input.Empty)
}
}
Action(bodyParser) {
request =>
val (innerAction, innerBody) = request.body
innerAction(request.map(_ => innerBody))
}
}
def username[A](check: RequestHeader => Option[String]): ((String) => Action[A]) => Action[(Action[A], A)] = intercept(check, r => Results.Unauthorized("not logged in"))
}
As you can see, the worker function intercept gives you the opportunity to compute some stuff based on the request content. Which computation result of type B might failed (Option), in that case a handler is there to tell what to do.
Having define what to compute, you can define your action using a function that takes a B and gives an Action[A].
The username method is just a simple predefined interceptor that can enables us to define how to retrieve the logged in username, just to illustrate.
Now here is how we can use both of them in your Controller
//index is defined for both GET and POST in routes, but fails on POST
// thanks to the interceptor that checks at first the used method
// the case mustn't be handled in the Action definition
def index = Interceptor.intercept(
/*check the method*/
request => if (request.method == "GET") Some(request.method) else None,
/*not a GET => bad request*/
request => BadRequest(request.method + " not allowed")
) { /*the computation result*/method => Action {
Ok("The method : " + method)
}
}
//this controller retrieve the username in the session and renders it in a OK response
def secured = Interceptor.username(r => r.session.get("username")) { username => Action {
Ok("You're logged in as " + username)
}
}
//this enables you to logged in => store in session
def login(u:String) = Action { request => {
Ok("Logged in as " + u) withSession(("username" -> u))
}
}
Now if you have a generic computation you can create your preconfigured interceptor (here I'm using a case class but simply defining a function that partially applies the interceptor is enough)
case class Intercept[B] (f: RequestHeader => Option[B], e: RequestHeader => Result) {
def apply[A](action: B => Action[A]) = Interceptor.intercept[A,B](f, e)(action)
}
val getInterceptor = Intercept[String](
request => if (request.method == "GET") Some(request.method) else None,
request => BadRequest(request.method + " not allowed")
)
def index2 = getInterceptor { method => Action {
Ok("Da method : " + method)
}
}
EDIT related to the comment:
Accordingly to your comment, here is how you could do using an interceptor (note that I've mocked up the host retrieval and checking)
Using hosted and anotherHosted, you'll be able to test this workflow:
/hosted/false?host=myhost => 404 because at first myhost is not cached and I provided false to the checked mockup
/hosted/true?host=myhost => not in cache but it will add it, and then no 404
/hosted/anotherHosted/false?host=myhost => in cache because it is hosted => no 404
/hosted/anotherHosted/false?host=notMyhost => 404
Here is the code
def getHost(request:RequestHeader) = request.queryString.get("host").get.head
def checkHost(host:String, b: Boolean) = b
val checkHosted = (b: Boolean) => Intercept[String](
request => {
val host = getHost(request)
Cache.getAs[String](host) match {
case x#Some(_) => x
case None => if (checkHost(host, b)) {
Cache.set(host, host)
Some(host)
} else None
}
},
request => NotFound(getHost(request) + "not hosted")
)
def hosted(b:String) = checkHosted(b.toBoolean) {
host => Action {
Ok("this host is ok : " + host)
}
}
def anotherHosted(b:String) = checkHosted(b.toBoolean) {
host => Action {
Ok("this host is ok : " + host)
}
}