Scala DeadboltAction andThen custom play ActionBuilder

364 views Asked by At

Here my problem: I need to check in a controller the user permission using deadbolt and then add something to the request (using an ActionBuilder). Normally using Play Action Builders would be (action1 andThen action2) but this doesn't work with DeadboltActions.

Here some code:

ActionBuilder

import javax.inject.Inject
import models.Item
import modules.item.services.ItemServiceClient
import play.api.mvc._

import scala.concurrent.{ExecutionContext, Future}

class ItemRequest[A](val items: Seq[Item], request: Request[A]) extends WrappedRequest[A](request)

class ItemAction @Inject()(val parser: BodyParsers.Default)(implicit val executionContext: ExecutionContext)
  extends ActionBuilder[ItemRequest, AnyContent] with ActionTransformer[Request, ItemRequest] {
  def transform[A](request: Request[A]): Future[ItemRequest[A]] = {
    ItemServiceClient.getItems.map{
      new ItemRequest(_, request)
    }recover{
      case _ => new ItemRequest(Seq(), request)
    }

  }
}

Controller:

@Singleton
class ItemController @Inject()(cc: ControllerComponents, deadbolt: DeadboltActions, itemAction: ItemAction) extends AbstractController(cc) with I18nSupport {

def createSomething: Action[AnyContent] = (deadbolt.Pattern("Create_Something", PatternType.EQUALITY) andThen itemAction) { implicit request: ItemRequest[AnyContent] =>
            Ok(modules.item.views.html.createSomething(Something.form, request.items))
    }
}

[error] Unapplied methods are only converted to functions when a function type is expected. You can make this conversion explicit by writing Pattern _ or Pattern(_,_,_,_,_)(_)(_) instead of Pattern.

[error] def createSomething: Action[AnyContent] = (deadbolt.Pattern("Create_Deck", PatternType.EQUALITY)() andThen itemAction).synchronized() { implicit request: ItemRequest[AnyContent] =>

Anyone who already dealt with this?

2

There are 2 answers

3
Jamie On

Since DeadboltActions#Pattern returns an Action, I don't think you can use it for Action composition. Instead, at least with Deadbolt 2.5.1, you may be looking for be.objectify.deadbolt.scala.SubjectActionBuilder, which is an ActionBuilder[AuthenticatedRequest], then you can compose that with your ItemAction.

Here's a simple example:

class MyRequest[A](request: Request[A]) extends WrappedRequest(request)
val handler: DeadboltHandler = ???
val action1: ActionFunction[Request, AuthenticatedRequest] = SubjectActionBuilder(None)
val action2: ActionTransformer[Request, MyRequest] = new ActionTransformer[Request, MyRequest] {
  override protected def transform[A](request: Request[A]): Future[MyRequest[A]] = Future.successful(new MyRequest(request))
}
val action3: ActionFunction[Request, MyRequest] = action1 andThen action2

Here's an example that accomplishes what I think you want to do, but does NOT use action composition (at least in the sense I think you mean):

class MyRequest[A](request: Request[A]) extends WrappedRequest[A](request)
class MyAction extends ActionTransformer[Request, MyRequest] {
  override protected def transform[A](request: Request[A]): Future[MyRequest[A]] =
    Future.successful(new MyRequest(request))
}

val deadboltActions: DeadboltActions = ???

def createSomething: Action[AnyContent] = deadboltActions.Pattern("Create_Something")() { authRequest =>
  ((new MyAction) compose Action).async { request: MyRequest[AnyContent] =>
    Future.successful(Ok(""))
  }(authRequest)
}
0
Mario Galic On

As Jamie mentioned, when out-of-the-box composition utility methods are not directly applicable we can fall back to composing Actions using the following pattern:

OuterAction { outerRequest =>
  InnerAction { request =>
    // ... some Result
  } (outerRequest)
}

For example, in your case the following might work:

val deadboltAction =
  deadbolt.Pattern[AnyContent](
    value = "admin.printer",
    patternType = PatternType.EQUALITY
  )() _

val itemAction = ...

deadboltAction { implicit authRequest: AuthenticatedRequest[AnyContent] =>
  itemAction { implicit request: ItemRequest[AnyContent] => 
    Ok(modules.item.views.html.createSomething(Something.form, request.items))
  } (authRequest)
}

To tidy it up a bit, we could create the following utility method

def deadboltActionWithItemAction(block: ItemRequest[AnyContent] => Result): Action[AnyContent] =
  deadboltAction { implicit authRequest =>
    itemAction { 
      block 
    }(authRequest)
  }

and then the call site looks something like so

deadboltActionWithItemAction { implicit request: ItemRequest[AnyContent] =>
  Ok(modules.item.views.html.createSomething(Something.form, request.items))
}