Scala Play Templates: Reverse routing with HTTP POST - scala

when reverse routing to a GET route, i simply do
Hello
this means theres a GET route to the sayHello function. What if it was POST and needed an attached payload? is the POST implicit? how is the payload data attached?

The HTTP method for the reverse router is derived from your route configuration file.
Here is an example from a route configuration where I have two different requests with the same URL but different HTTP methods pointing to different methods:
GET /login controllers.Application.login
POST /login controllers.Application.authenticate
The login() method in the Application controller simply streams out the HTML form:
def login = Action { implicit request =>
Ok(html.loginForm(loginForm))
}
the authenticate() method however binds the request to a form allowing further processing:
def authenticate = Action { implicit request =>
loginForm.bindFromRequest.fold(
formWithErrors => BadRequest(html.loginForm(formWithErrors)),
user => {
// OTHER CODE HERE
Redirect(routes.Blog.createPost).withSession("user" -> user)
}
)
}
This second method requires a form definition in the controller:
val loginForm = Form(
tuple(
"username" -> text,
"password" -> text
) verifying ("Invalid username or password", result => result match {
case (username, password) => Account.authenticate(username, password).isDefined
})
)
So depending which method you put to the reverse router in your view, that is the HTTP method which will be used for the request.

Related

How to unit test OAuth2BearerToken in route header in akka http

i have a route which needs an access token in its header to grant access to it for that i have this working code
def accessProtectedResource: server.Route =
path("access-protected-resource") {
get {
bearerToken { token =>
token match {
case Some(tokenValue) =>
complete(OK, routeResponseMessage.getResponse(OK.intValue,ServerMessages.AUTH_PASS,JsObject.empty))
case None => reject(AuthorizationFailedRejection)
}
}
}
}
private def bearerToken: Directive1[Option[String]] =
for {
authBearerHeader <- optionalHeaderValueByType(classOf[Authorization]).map(extractBearerToken)
xAuthCookie <- optionalCookie("X-Authorization-Token").map(_.map(_.value))
} yield authBearerHeader.orElse(xAuthCookie)
private def extractBearerToken(authHeader: Option[Authorization]): Option[String] =
authHeader.collect {
case Authorization(OAuth2BearerToken(token)) => token
}
when i hit the route through postman in the Authorization tab i selected the type to Bearer Token and add the token and send the request and everything works fine now i want to unit test this route
for this i am looking at this
but i am confused how can i add the header in a proper way in my unit test here is my code
"pass route /access-protected-resource" in {
routeResponseMessage.getResponse(OK.intValue, ServerMessages.AUTH_PASS, JsObject.empty)
val originHeader = Authorization(OAuth2BearerToken("accessTokenString"))
Get("http://0.0.0.0:8083/get-user-token") ~> originHeader ~> authenticationController.route ~> check {
}
}
but my route is getting rejected
- pass route /access-protected-resource *** FAILED ***
[info] Request was rejected with rejection MethodRejection(HttpMethod(POST)) (CheckValidUserTokenExistsTest.scala:76)
how to do this correctly ?
How about using addCredential and testing only bearerToken method wrapped with Route.seal like this(https://github.com/ItoYo16u/scala-usage-playground/blob/master/src/test/scala/web/akka/JWTDirectivesSpec.scala)?

Read full request URL in Play Web Framework

I am trying to read full URL which comes with #params. How can i read in Play controller? I am able to read query parameters but not route directive
ex : https://x.app.com/callback#x=1
Action.async{ implicit request =>
val fullUrl = ??? //Read full url of request
}
I tried request.uri and request.path. both are not helpful.
response of request.uri => /callback
response of request.path => /callback
There are no #params in the response.
you can use:
request.path
or
request.uri
You cant pass fragment (# part) to server, it is browser only. Use query parameter instead

Play Framework 2.6 CSRF and Session

I got strange issue. I'm implementing cart functionality on my website and I use session to store cart positions. I have a POST action to add new position to cart, and I have CSRF filter enabled to secure website. I call it with ajax on a product page, so first call is okay, but second says Unauthorized and in logs there are [CSRF] Check failed because no token found in headers for /cart. But it has. I call it with:
$("form").submit(function(e){
e.preventDefault();
$.ajax({
url: '/cart',
method: 'POST',
data: getCartPosition(),
beforeSend: function(xhr){xhr.setRequestHeader('Csrf-Token', $('input[name=csrfToken]').val());},
success: function (data, textStatus) {
alert('Added!');
},
error: function (error) {
alert('Error!');
}
});
});
and I put CSRF token in template somewhere:
#CSRF.formField
and it's in request:
I have enabled this in config
play.filters.csrf.bypassCorsTrustedOrigins=true
play.filters.hosts {
# Allow requests to example.com, its subdomains, and localhost:9000
allowed = ["localhost:9000", "localhost:4200"]
}
But what is strange that it seems it puts csrfToken in session, because after failed request I have session like this
Session(Map(cart -> {"positions":
[{"trackId":1},{"trackId":24},{"trackId":20}]},
username -> user,
token -> 0639d0b0-e7c8-4e82-9aad-2a43044e72db,
csrfToken -> e705413843ea96a6491a0e9e800ba36a712c4f70-1506542471068-0baeef7535eb9c889fb6fed2))
Idk why it's there, my add2cart action looks like:
private def cartAction(addToCartForm: Form[CartPosition], action: (Cart, CartPosition) => Cart)(implicit request: UserRequest[Any]) = {
addToCartForm.fold(
_ => BadRequest("Error!"),
position => {
getCart match {
case Some(cart) => Ok("Ok").withSession("cart" -> Json.toJson(action(cart, position)).toString(), "username" -> request.session.get("username").getOrElse(""), "token" -> request.session.get("token").getOrElse(""))
case _ => Ok("Ok, no").withSession("cart" -> Json.toJson(action(Cart(Seq.empty), position)).toString())
}
}
)
}
def addToCart() = guestAction { implicit request =>
cartAction(addToCartForm.bindFromRequest, addCartPos)
}
and addCartPos just adds position to json
I've got same issue with Play 2.7.3.
In my case, the form is generated by the Twirl with the csrf token and because I'm using ajax to submit the form, I've copied the csrf token from the rendered form and pass it to the ajax header as writen in the Play's documentation.
The form can be submitted multiple times so I need to updated the token. Therefore I'm passing through ajax response new csrf token taken in the controller from play.filters.csrf.CSRF.getToken.get.value.
But unfortunatelly, the second submission failed as cutoffurmind mentioned.
And the fix is as described by Knut Arne Vedaa to add new token to the session. I did it by the withSession method.
So the controller response is looking like this:
Ok(Json.obj(
"status" -> (user != None),
"notif" -> "Success login",
"data" -> Map(
"adminUrl" -> "www.something ...",
"csrf" -> play.filters.csrf.CSRF.getToken.get.value
)
)).withSession(
"uid" -> user.getOrElse(User()).id.toString,
"csrfToken" -> play.filters.csrf.CSRF.getToken.get.value
)
It doesn't look like an issue as Play Framework doesn't have session data kept on the server, so it is logical that the token has to be updated in the client site after the ajax request. The main issue is that it is not mentioned in the documentation (in the CSRF ajax section), what could be handy as people simply doesn't read the doc from A to Z in expected order.
In my case the solution was to set the play.filters.csrf.cookie.name config option to a value other than null:
play.filters.csrf.cookie.name = csrf_token

Silhouette and mobile application

I've used as example play-silhouette-angular-seed.
Authorization via Satellizer works fine.
When I try to authorize via iOs app I got next error:
com.mohiva.play.silhouette.impl.exceptions.UnexpectedResponseException:
[Silhouette][facebook] Cannot build OAuth2Info because of invalid response format:
List((/access_token,List(ValidationError(List(error.path.missing),WrappedArray()))))
I got an error 400 in this function from OAuth2Provider.scala :
protected def getAccessToken(code: String)(implicit request: RequestHeader): Future[OAuth2Info] = {
httpLayer.url(settings.accessTokenURL).withHeaders(headers: _*).post(Map(
ClientID -> Seq(settings.clientID),
ClientSecret -> Seq(settings.clientSecret),
GrantType -> Seq(AuthorizationCode),
Code -> Seq(code),
RedirectURI -> Seq(resolveCallbackURL(settings.redirectURL))) ++ settings.accessTokenParams.mapValues(Seq(_))).flatMap { response =>
logger.debug("[Silhouette][%s] Access token response: [%s]".format(id, response.body))
Future.from(buildInfo(response))
}
}
This error has been risen because Satellizer for authentication via Facebook send to server an 'authentication code' and Silhouette server use this code to get Facebook 'access token' and create user.
Facebook iOs SDK, instead, obtained 'Access token' and I've tried to send it to server in Json in field 'code' like 'Satellizer.
To resolve this issue I send an 'access token' in Json field named 'access_token' and use next code to authenticate mobile application:
class MobileSocialAuthController #Inject() (
val messagesApi: MessagesApi,
userService: UserService,
authInfoRepository: AuthInfoRepository,
socialProviderRegistry: SocialProviderRegistry,
val env: Environment[User, JWTAuthenticator])
extends Silhouette[User, JWTAuthenticator]
{
def authenticate(provider: String) = UserAwareAction.async(parse.json) {
implicit request =>
provider match {
case "facebook" =>
request.body.asOpt[OAuth2Info] match {
case Some(authInfo) =>
(socialProviderRegistry.get[FacebookProvider](provider) match {
case Some(p: FacebookProvider) =>
for {
profile <-p.retrieveProfile(authInfo)
user <- userService.save(profile)
authInfo <- authInfoRepository.save(profile.loginInfo, authInfo)
authenticator <- env.authenticatorService.create(profile.loginInfo)
token <- env.authenticatorService.init(authenticator)
} yield {
env.eventBus.publish(LoginEvent(user, request, request2Messages))
Ok(Json.obj("token" -> token))
}
case _ => Future.failed(new ProviderException(s"Cannot authenticate with unexpected social provider $provider"))
}).recover {
case e: ProviderException =>
logger.error("Unexpected provider error", e)
Unauthorized(Json.obj("message" -> Messages("could.not.authenticate")))
}
case _ =>
Future(BadRequest(Json.obj(
"message" -> "Bad OAuth2 json.")))
}
case _ =>
Future(BadRequest(Json.obj(
"message" -> "You can use only Facebook account for authentication.")))
}
}
}
As a result, I have a token which I use in ios application to obtain resources.
This happens when the OAuth2Provider gets a response it can't parse, which is, any non-success response. So there can be many reasons for this error, for instance the authorization code is invalid or expired, or you haven't configured the redirect_uri properly (check your Facebook app configuration on the Facebook dev site to set the redirect_uri).
Silhouette does log the response it gets from Facebook which should help you debug what the actual issue is, the log line to look for is in the snippet you provided:
logger.debug("[Silhouette][%s] Access token response:...
So check your logs, there you should see the response from Facebook, likely with an error indicating why they couldn't give you an access_token.

control not entering in fold() method

i am trying to insert data from form to database but on form submission control does not enter in fold(error, success) method and runs the statement after it and redirects it to other page
this is my controller method
def submitinfo = Action { implicit request =>
signupForm.bindFromRequest().fold(
errors => BadRequest(views.html.signup(errors)),
data => {
println("************enter sucess case *********************")
signupcc.insertData(data.name, data.username, data.email, data.password)
})
println("************Redirecting to sucess page *********************")
Redirect(routes.Application.success)
}
here is my routes file
# Home page
GET / controllers.Application.index
GET /signup controllers.Application.signup
POST /submit controllers.Application.submitinfo
GET /success controllers.Application.success
GET /signin controllers.Application.signin
please tell me what i am doing wrong
Redirect action should be part of sucess case inside fold method:
def submitinfo = Action { implicit request =>
signupForm.bindFromRequest().fold(errors => BadRequest(views.html.signup(errors)),
data => {
println("************enter sucess case *********************")
signupcc.insertData(data.name, data.username, data.email, data.password)
println("************Redirecting to sucess page *********************")
Redirect(routes.Application.success)
})
}