I'm trying to cached the Unauthorized result from the auth builder, but i can't find a way to properly request the result from cache (without using redirect with reverse route) any idea? this is my code:
object Authenticated extends AuthenticatedBuilder(
request =>
request.session.get("email"),
request =>
Unauthorized(html.index("fail!", loginForm))
)
I want to do something like:
object Authenticated extends AuthenticatedBuilder(
request =>
request.session.get("email"),
request =>
Cached("Fail") { Action { implicit request =>
Unauthorized(html.index("fail!", loginForm))
}
}
)
this will of course return Cached instead of simpleResult and fail...
btw i'm using play 2.2.1
Thanks to #Peter i was able to return a SimpleResult with getOrElse, my final code is a bit more complex but here is an example that hopefully will help more people:
import play.api.cache.Cache
import play.api.Play.current
import play.api.mvc.Security.AuthenticatedBuilder
object Authenticated extends AuthenticatedBuilder(
request =>
request.session.get("email"),
request =>
Cache.getOrElse("UnAuth"){
Unauthorized("Fail")
}
)
Related
In my Scalatra routes, I often use halt() to fail fast:
val user: User = userRepository.getUserById(params("userId"))
.getOrElse {
logger.warn(s"Unknown user: $userId")
halt(404, s"Unknown user: $userId")
}
As shown in the example, I also want to log a warning in those cases. But I'd like to avoid the code duplication between the halt() and the logger. It would be a lot cleaner to simply do:
val user: User = userRepository.getUserById(params("userId"))
.getOrElse(halt(404, s"Unknown user: $userId"))
What would be the best way of logging all "HaltExceptions" in a cross-cutting manner ?
I've considered:
1) Overriding the halt() method in my route:
override def halt[T](status: Integer, body: T, headers: Map[String, String])(implicit evidence$1: Manifest[T]): Nothing = {
logger.warn(s"Halting with status $status and message: $body")
super.halt(status, body, headers)
}
Aside from the weird method signature, I don't really like this approach, because I could be calling the real halt() by mistake instead of the overridden method, for example if I'm halting outside the route. In this case, no warning would be logged.
2) Use trap() to log all error responses:
trap(400 to 600) {
logger.warn(s"Error returned with status $status and body ${extractBodyInSomeWay()}")
}
But I'm not sure it's the best approach, especially since it adds 201 routes to the _statusRoutes Map (one mapping for each integer in the range...). I also don't know how to extract the body here ?
3) Enable some kind of response logging in Jetty for specific status codes ?
What would be the best approach to do this? Am I even approaching this correctly?
The easiest solution is doing it in a servlet filter like below:
package org.scalatra.example
import javax.servlet._
import javax.servlet.http.HttpServletResponse
class LoggingFilter extends Filter {
override def init(filterConfig: FilterConfig): Unit = ()
override def destroy(): Unit = ()
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = {
chain.doFilter(request, response)
val status = response.asInstanceOf[HttpServletResponse].getStatus
if (status >= 400 && status <= 600) {
// Do logging here!
}
}
}
Register this filter in your Bootstrap class (or it's possible even in web.xml):
package org.scalatra.example
import org.scalatra._
import javax.servlet.ServletContext
class ScalatraBootstrap extends LifeCycle {
override def init(context: ServletContext): Unit = {
context.addFilter("loggingFilter", new LoggingFilter())
context.getFilterRegistration("loggingFilter")
.addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
// mount your servlets or filters
...
}
}
In my opinion, Scalatra should provide a way to trap halting easier essentially. In fact, there is a method named renderHaltException in ScalatraBase, it looks to be possible to add logging by overriding this method at a glance:
https://github.com/scalatra/scalatra/blob/cec3f75e3484f2233274b1af900f078eb15c35b1/core/src/main/scala/org/scalatra/ScalatraBase.scala#L512
However we can't do it actually because HaltException is package private and it can be accessed inside of org.scalatra package only. I wonder HaltException should be public.
I'm using akka-http for the first time - my usual web framework of choice is http4s - and I'm having trouble getting the way I usually write endpoint unit tests to work with the route testing provided by akka-http-testkit.
Generally, I use ScalaTest (FreeSpec flavour) in order to set up an endpoint call and then run several separate tests on the response. For akka-http-testkit, this would look like:
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.testkit.ScalatestRouteTest
import org.scalatest.{FreeSpec, Matchers}
final class Test extends FreeSpec with ScalatestRouteTest with Matchers {
val route: Route = path("hello") {
get {
complete("world")
}
}
"A GET request to the hello endpoint" - {
Get("/hello") ~> route ~> check {
"should return status 200" in {
status should be(StatusCodes.OK)
}
"should return a response body of 'world'" in {
responseAs[String] should be("world")
}
//more tests go here
}
}
}
This errors with
java.lang.RuntimeException: This value is only available inside of a `check` construct!
The problem is the nested tests inside the check block - for some reason, values like status and responseAs are only available top-level within that block. I can avoid the error by saving the values I'm interested in to local variables top-level, but that's awkward and capable of crashing the test framework if e.g. the response parsing fails.
Is there a way around this, without putting all my assertions into a single test or performing a new request for each one?
You can group your test like that
"A GET request to the hello endpoint should" in {
Get("/hello") ~> route ~> check {
status should be(StatusCodes.OK)
responseAs[String] should be("world")
//more tests go here
}
}
I'm working with playframework for final project at university and I'm getting a problem when routing a delete or put method.
When I'm requesting a DELETE or PUT methods I'm getting:
[info] play.api.Play - Application started (Dev)
[debug] a.ErrorHandler - onClientError: statusCode = 404, uri = /Rest/deleteCity, message ="
My JQuery ajax call is:
$("#scalaDelete").click(function(){
$("#result").empty();
$.ajax({
url: "http://localhost:9000/Rest/deleteCity",
method: "DELETE",
data: {city: "Alvorada"},
dataType: "json",
success: function(result){
$("#result").append("Result: "+result.Result);
},
error: function (request, status, error) {
alert(status);
}
});
});
My Route Play Route:
DELETE /Rest/deleteCity controllers.RestController.deleteCity()
My Controller Method:
case class UserDelete(city:String)
class RestController #Inject()(db: Database, cc: ControllerComponents) extends AbstractController(cc) {
val userDeleteForm = Form(
mapping(
"city" -> text
)(UserDelete.apply)(UserDelete.unapply)
)
def deleteCity = Action{ implicit request=>
val userPar = userDeleteForm.bindFromRequest.get
//DatabaseDelete
Ok(jsonResult)
}
}
I've already activated cross domain in chrome, I've used a CORS extension for it.
Thanks for helping
This seems related to Restful http delete in play, i.e. DELETE with data can be sketchy.
Instead of passing data, I would just move this to the url:
DELETE /Rest/deleteCity/:city controllers.RestController.deleteCity(city: String)
# or with a query string
DELETE /Rest/deleteCity controllers.RestController.deleteCity(city: String)
and then do
http://localhost:9000/Rest/deleteCity/Alvorada
# or with a query string
http://localhost:9000/Rest/deleteCity?city=Alvorada
Personally I prefer the latter.
I agree with #AndyHayden.
Play ignores the body of the DELETE request, that is the correct behavior to my mind, but you can work around by explicitly passing a body parser:
def delete = Action(parse.json) { implicit request =>
val json = request.body
val someProp = (json \ "someprop").as[String]
Ok(s"Prop is: $someProp")
}
(this example was given by one of the developers of the Play itself:
https://github.com/playframework/playframework/issues/4606#issuecomment-109192802.)
About the doubts in comments:
I've seen another post here where a guy said some browsers just support get and post method.
POST and GET are only valid for the method attribute of the form tag.
You are using javascript request, so you can use any method that server supports. i.e. DELETE is completely fine there.
But something interesting for you to know is that playframework uses akka and this framework does not support DELETE request for security reasons, in fact it wasn't well explained on post. Then if you wanna make a DELETE method you gotta make a post method for complete your code.
Akka HTTP supports the DELETE request (as well as Play Framework): https://doc.akka.io/docs/akka-http/current/scala/http/routing-dsl/directives/method-directives/delete.html
After upgrading my project to Play Framework 2.5.4 from 2.4.8, I'm having issue while working with session. There are to methods in my controller to test the issue:
def test = Action { implicit request =>
Redirect(controllers.routes.Carts.test2()).withSession(new Session(Map("user" -> "customer")))
}
def test2 = Action { implicit request =>
Ok(request.session.data.toString())
}
Basically test method adds session and redirects to test2 method. When I open page with test method url after redirect I'm getting what I need in the browser:
Map(user -> customer)
But if I refresh page after that I'm getting:
Map(csrfToken ->
ce1a6222484f378d38ab3534c2b400191270395d-1470238791014-c988ce3fe47259173166949a)
So, seems like session works for one request only and then overwrites with csrfToken. I have disabled all the filters. My class with filters looks like this:
class Filters #Inject() () extends HttpFilters {
val filters = Seq.empty
}
Can't understand what is wrong with my code. The same code was working fine before upgrade.
Check in ur application.conf if session.secure is true bring it to
play.http.session.secure=false
What’s the preferred way to handle 404 errors with Play 2.0 and show a nice templated view?
You can override the onHandlerNotFound method on your Global object, e.g.:
object Global extends GlobalSettings {
override def onHandlerNotFound(request: RequestHeader): Result = {
NotFound(views.html.notFound(request))
}
}
Please note that there are really two different problems to solve:
Showing a custom 404 page when there is "no handler found", e.g. when the user goes to an invalid URL, and
Showing a custom 404 (NotFound) page as a valid outcome of an existing handler.
I think the OP was referring to #2 but answers referred to #1.
"No Handler Found" Scenario
In the first scenario, for "no handler found" (i.e. invalid URL), the other answers have it right but to be more detailed, per the Play 2.1 documentation as:
Step 1: add a custom Global object:
import play.api._
import play.api.mvc._
import play.api.mvc.Results._
object Global extends GlobalSettings {
override def onHandlerNotFound(request: RequestHeader): Result = {
NotFound(
views.html.notFoundPage(request.path)
)
}
}
Step 2: add the template. Here's mine:
#(path: String)
<html>
<body>
<h1>Uh-oh. That wasn't found.</h1>
<p>#path</p>
</body>
</html>
Step 3: tweak your conf/application.conf to refer to your new "Global". I put it in the controllers package but it doesn't have to be:
...
application.global=controllers.Global
Step 4: restart and go to an invalid URL.
"Real Handler can't find object" Scenario
In the second scenario an existing handler wants to show a custom 404. For example, the user asked for object "1234" but no such object exists. The good news is that doing this is deceptively easy:
Instead of Ok(), surround your response with NotFound()
For example:
object FruitController extends Controller {
def showFruit(uuidString: String) = Action {
Fruits.find(uuidString) match {
case Some(fruit) => Ok(views.html.showFruit(fruit))
// NOTE THE USE OF "NotFound" BELOW!
case None => NotFound(views.html.noSuchFruit(s"No such fruit: $uuidString"))
}
}
}
What I like about this is the clean separation of the status code (200 vs 404) from the HTML returned (showFruit vs noSuchFruit).
HTH
Andrew
If you want to do the same using Java instead of Scala you can do it in this way (this works for play framework 2.0.3):
Global.java:
import play.GlobalSettings;
import play.mvc.Result;
import play.mvc.Results;
import play.mvc.Http.RequestHeader;
public class Global extends GlobalSettings {
#Override
public Result onHandlerNotFound(RequestHeader request) {
return Results.notFound(views.html.error404.render());
}
}
Asumming that your 404 error template is views.html.error404 (i.e. views/error404.scala.html).
Please note that Play development team are making lots of efforts to move away from global state in Play, and hence GlobalSettings and the application Global object have been deprecated since version 2.4.
HttpErrorHandler.onClientError should be used instead of
GlobalSettings.onHandlerNotFound. Basically create a class that inherits from HttpErrorHandler, and provide an implementation for onClientError method.
In order to find out type of error (404 in your case) you need to read status code, which is passed as a one of the method arguments e.g.
if(statusCode == play.mvc.Http.Status.NOT_FOUND) {
// your code to handle 'page not found' situation
// e.g. return custom implementation of 404 page
}
In order to let Play know what handler to use, you can place your error handler in the root package or configure it in application.conf using play.http.errorHandler configuration key e.g.
play.http.errorHandler = "my.library.MyErrorHandler"
You can find more details on handling errors here: for Scala or Java.
This works in 2.2.1. In Global.java:
public Promise<SimpleResult> onHandlerNotFound(RequestHeader request) {
return Promise.<SimpleResult>pure(notFound(
views.html.throw404.render()
));
}
Ensure that you have a view called /views/throw404.scala.html
This works in 2.2.3 Play - Java
public Promise<SimpleResult> onHandlerNotFound(RequestHeader request) {
return Promise<SimpleResult>pure(Results.notFound(views.html.notFound404.render()));
}
html should be within /views/notFound404.scala.html
Dont forget to add Results.notFounf() and import play.mvc.Results;
For Java, if you want to just redirect to main page, I solved it by this.
#Override
public Promise<Result> onHandlerNotFound(RequestHeader request) {
return Promise.pure(redirect("/"));
}