I am testing Deadbolt in a Scala Play Application. My controller methods look something like this:
def getProject(projectId: Int) = actionBuilder.RestrictAction("user").defaultHandler() {
authRequest =>
//retrieves project
}
In this case, I only want the user to be authorized to get the project if the projectId belongs to this user. Other more complex cases involve multiple parameters both from the query string and/or the post body.
From what I understand, the approach here would be to pass the parameters to a DynamicResourceHandler and then handling the permissions for each case individually. It is feasible, but I was expecting a bit more support from Deadbolt for this use case. What is the best approach to authorize a request based on the parameters received?
Deadbolt stays fairly neutral to avoid forcing developers into a specific style, but in this case you can use the meta argument to pass the information into the constraint. The definition is
object DynamicAction {
def apply(name: String, meta: Option[Any] = None): DynamicAction.DynamicActionBuilder = DynamicActionBuilder(name, meta)
case class DynamicActionBuilder(name: String, meta: Option[Any] = None) extends DeadboltActionBuilder {
override def apply[A](bodyParser: BodyParser[A])(block: AuthenticatedRequest[A] => Future[Result])(implicit handler: DeadboltHandler) : Action[A] =
deadboltActions.Dynamic(name, meta, handler)(bodyParser)(block)
}
}
so your controller function will look something like this
def getProject(projectId: Int) = actionBuilder.DynamicAction(name = "checkProject", meta = Some(projectId)).defaultHandler() {
authRequest =>
//retrieves project
}
This will get a DynamicResourceHandler (I've come to hate that name, I may change it in some future version) and invoke your implementation of this function
def isAllowed[A](name: String,
meta: Option[Any] = None,
deadboltHandler: DeadboltHandler,
request: AuthenticatedRequest[A]): Future[Boolean]
You will need to use asInstanceOf for the meta value.
For more complex requirements, you can pass whatever you have assembled the data into (a case class or map, for example) as the meta argument.
Related
Combining an ActionBuilder that transforms a request into a custom WrappedRequest with an additional type parameter, and then combining that with an ActionFilter causes the type of the custom WrappedRequest to be dropped.
Why is this and is there a fix?
For example, lets say I need to an authentication ActionBuilder and an optional, authorisation ActionFilter where the user type we need can vary depending on use.
See this contrived code:
case class AuthRequest[A, U](val request: Request[A], val user: U) extends WrappedRequest[A](request)
case class TestUser(id: String)
case class AuthenticationAction[A]() extends ActionBuilder[({type λ[B] = AuthRequest[B, TestUser]})#λ] {
override def invokeBlock[A](request: Request[A], block: (AuthRequest[A, TestUser]) => Future[Result]): Future[Result] =
block(AuthRequest(request, TestUser("test-id")))
}
case class AuthorisedAction[A]() extends ActionFilter[({type λ[B] = AuthRequest[B, TestUser]})#λ] {
// does some additional authorisation checks for added security
def authorised(user: TestUser) = true
override def filter[A](request: AuthRequest[A, TestUser]) = Future.successful {
if( authorised(request.user) ) None
else Some(Unauthorized)
}
}
Please note: The type function in the above is required as per this answer because of the additional type parameter on the AuthRequest. Again this is required because this API will be used with more than one type of user.
If I then implement a controller that uses the above:
class ExampleController extends Controller {
def secured = (AuthenticationAction() andThen AuthorisedAction()) { request =>
Ok(request.user.id)
}
}
I get a compiler error (value user is not a member of play.api.mvc.WrappedRequest[play.api.mvc.AnyContent]). In other words, the type of the variable request above is WrappedRequest instead of the expected type of AuthRequest[play.api.mvc.AnyContent, TestUser]!
I understand that most use cases the AuthenticationAction and AuthorisedAction would be combined into a single action but, because authorisation is optional, I would like to keep these as separate concerns.
Is this possible? Is this a bug in the Play Framework API? Or a Scala bug? Or human error?
Using your example I was able to compose the actions like this:
class ExampleController extends Controller {
def secured = AuthorisedAction().compose(AuthenticationAction()) { request =>
Ok(request.user.id)
}
}
It's interesting to note that in both cases IntelliJ's type inspection sees request as being of type AuthRequest[AnyContent, TestUser] -- it's only scalac that sees it as a WrappedRequest.
I have a view that takes in an implicit Lang, needed for Play's Messages object. The signature of this view is:
#()(implicit lang: Lang)
Then I have a controller that needs to create the view:
def createView = Action { request =>
Ok(views.html.showView())
}
And then I have an implicit method that I want called to create the Lang that the view needs:
implicit def getLangFromRequest(request: RequestHeader): Lang = {
request.cookies.get("lang") match {
case Some(cookie) => Lang(cookie.value)
case None => Lang("en") // Default
}
}
In other words, the method is supposed to get the language from the cookie if it can or use a default otherwise (I've trimmed out error handling for this question).
However, this implicit conversion isn't taking place. I know that it's able to implicitly convert, because by creating an implicit variable in the controller works:
def createView = Action { request =>
implicit val lang: Lang = request
Ok(views.html.showView())
}
But I'd rather not have to add this boiler plate line everywhere. Presumably I must explicitly call the method because there's some global Lang object that is being used before an implicit conversion.
What are my options here to avoid having to repeat this implicit val lang: Lang = request in every controller?
According to Play documentation
If you have an implicit Request in the scope, it will provide an
implicit Lang value corresponding to the preferred language extracted
from the Accept-Language header and matching one of the application
supported languages.
So you need to change your action to
def createView = Action { implicit request =>
Ok(views.html.showView())
}
and it should work.
Having to call implicit val lang: Lang = request for each controller seems like a lot of work but when you think about it, not really, you already call the Action method (which accepts as a parameter a Request => Result function) whenever you create a new action.
Write a trait that would do all the heavy logic of finding the language, mix it into all controllers and instead of using the Action method, use a method that you write in that trait, that takes in a Request => Lang => Result function, does the logic for the languages and then applies the function parameter to the request in scope. Something like this:
trait Internationalization { subj: Controller =>
def withLanguage(f: => Request[AnyContent] => Lang => Result) = Action { implicit request =>
val lang = request.cookies.get("lang") match {
case Some(cookie) => Lang(cookie.value)
case None => Lang("en") // Default
}
f(request)(lang)
}
}
Then, instead of def action = Action { ... }, just mix in the trait and use:
def action = withLanguage { request => implicit lang =>
Ok(views.html.showView())
}
Background
I am trying to understand best practices for bringing implicit objects into scope within a Scala application.
I have a Playframework 2.2.0 (Scala 2.10) web app that mixes in a trait for Authorization. It checks. The Authenticated object checks that there is a user_id in scope, attempts to retrieve the user info, access token, and a data package object called a MagicNotebook from cache, database, and web service call. If the request is valid, then various objects are added to the wrapped request.
object Authenticated extends ActionBuilder[AuthenticatedRequest] {
def invokeBlock[A](request: Request[A],
block: (AuthenticatedRequest[A] => Future[SimpleResult])) = {
request.session.get(userName).map { implicit userId =>
Cache.getAs[DbUser](userKey).map { user =>
Cache.getAs[String](accessTokenKey).map { accessToken =>
Cache.getAs[MagicNotebook](magicNotebookKey(userId)).map { notebook =>
block(AuthenticatedRequest(user, accessToken, notebook, request) )
}.getOrElse(startOver)
}.getOrElse {
requestNewAccessToken(user.token).flatMap { response =>
persistAccessToken(response).map { accessToken =>
Cache.getAs[MagicNotebook](magicNotebookKey(userId)).map { notebook =>
block(AuthenticatedRequest(user, accessToken, notebook, request))
}.getOrElse(startOver)
}.getOrElse(startOver)
}
}
}.getOrElse(startOver) // user not found in Cache
}.getOrElse(startOver) // userName not found in session
}
}
}
case class AuthenticatedRequest[A](user: DbUser,
accessToken: String,
magic: MagicNotebook,
request: Request[A])
extends WrappedRequest[A](request)
Question
What is the best way to bring these implicit variables into scope?
Through an implicit class?
I tried to use an implicit companion class, with the following code:
object Helper {
implicit class Magical(request: AuthenticatedRequest[AnyContent]) {
def folderMap = request.magic.fMap
def documentMap = request.magic.dMap
}
}
However, I don't really get the benefit of an implicit this way:
def testing = Authenticated { implicit request =>
import services.Helper._
request.magic.home.folders // doesn't compile
request.magic.home.folders(Magical(request).ffMap) // compiles, but not implicitly
Ok("testing 123")
}
Through an import statement?
One possibility I considered was through an import statement within the controller. Here, the request has a MagicNotebook object in scope that I would like to use as an implicit variable.
def testing = Authenticated { implicit request =>
import request.magic._
request.magic.home.folders // implicit map is a parameter to the `folder` method
Ok("testing 123")
}
Through a companion trait?
Here, I create a companion trait that is mixed into the Authenticate trait that includes the two maps of the MagicNotebook object into scope of the controller.
trait Magic {
implicit def folderMap[A](implicit request: AuthenticatedRequest[A]) =
request.magic.fMap
implicit def docMap[A](implicit request: AuthenticatedRequest[A]) =
request.magic.dMap
}
My preference is the companion trait solution, but I was wondering if there might be a better way that I overlooked. I ended up re-writing methods that use the implicit variable, to use the MagicNotebook's two maps instead of whole object as implicit parameters.
But again, I was wondering if there might be a better way.
I am quite partial to package objects for this sort of thing. See What's New in Scala 2.8: Package Objects for a description. Package objects effectively allow you to put implicit classes into a package, which you can't otherwise do.
However, the main snag with this approach is that you can't split the definition of an object across multiple source files, so because the implicit classes need to be defined within the package object, they also need to be all in the same source file. If you have many implicit classes you wish to have imported, this can result in a large and unwieldy source file. However, that in itself is a sign that you have a “package god object” which should be split.
One of the ways I know of defining implicits is by using Package Objects.
package implicitIdiomatic {
implicit def nullableLongToOption(l:java.lang.Long) = Option(l)
}
}
package implicitIdiomatic
class ImplicitIdiomaticTest{
val l:Long = 1
longOpt(l)
def longOpt(l:Option[Long]) = l match {case Some(l1) => println(l1); case None => println("No long")}
}
Kind of useless example but hope you get the idea. Now when longOpt gets l, it is converted to Option[Long] using the implicit.
As long as you are working in the same package as defined by the package object you don't need the import statement.
I have:
case class One(someParam: String) {
private val _defaultTimeout = readFromConfig("defaultTimeout")
val timeout: Timeout = akka.util.Timeout(_defaultTimeout seconds)
val info: Option[Info] = Await.result(someSmartService.getInformationForSomething(someParam)), timeout.duration)
}
I'm building a service, which will obscure (encrypt) some sensitive data. I'm doing it in a such way:
def encrypt(oldOne: One): One = {
val encryptedSomeParam = EncryptService.getHash(oldOne.someParam)
val encryptedInfo = encryptInfo(oldOne.info)
// what to do with that? ^^
one.copy(someParam = encryptedSomeParam)
}
Also, I need to encrypt some data inside this "info" field of class One. The issue is that it is a val and I cannot reassign the value of a val. Is there an easy way how to do that? For now I'm thinking about changing it to a var, but I think it's not the best way to do that. Also, I cannot write encrypted data to this value from the beginning like this:
val info: Option[Info] = EncryptionService.encrypt(someSmartService.getInformationForSomething(someParam))
As this field is used in other places where I need the fields to be not encrypted. I want to encrypt sensitive data before the persistence of the object to a database.
Any ideas?
Thanks in advance!
EDIT: I know, that this looks like a bad design, so if someone has a better idea how to deal with it I'm looking forward to hear from you :)
Why not make info a case class argument as well?
case class One(someParam: String, info: Option[Info])
You could implement a default value for info by defining the companion object like
object One {
def apply(someParam: String): One = One(someParam, someSmartService.getInformationForSomething(someParam))
}
That would allow you to work with Ones as follows:
One("foo")
One("foo", Some(...))
One(encryptedSomeParam, encryptedInfo)
One("plaintext").copy(someParam = encryptedSomeParam, info = encryptedInfo)
EDIT 1: Lazy info
Case classes cannot have lazy val arguments, i.e., neither info: => Option[String] nor lazy val info: Option[String] is allowed as an argument type.
You could make info a parameter-less function, though
case class One(someParam: String, info: () => Option[String])
object One {
def apply(someParam: String): One = One(someParam, () => Some(someParam))
}
and then use it as
One("hi", () => Some("foo"))
println(One("hi", () => None).info())
This is obviously not ideal since it is not possible to introduce these changes without breaking code client code. Better solutions are welcome.
EDIT 2: Lazy info, no case class
If you don't insist on One being a case class (for example, because you really need copy), you could use a regular class with lazy values and a companion object for easy use:
class One(_someParam: String, _info: => Option[String]) {
val someParam = _someParam
lazy val info = _info
}
object One {
def apply(someParam: String): One = new One(someParam, Await.result(...))
def apply(someParam: String, info: => Option[String]): One = new One(someParam, info)
def unapply(one: One) = Some((one.someParam, one.info))
}
Hitting a catch 22 situation due to lack of generics support in Play template layer.
I have several shopping cart screens that all require a User and a Payment + optional custom fields.
case class Conference(
user: User,
payment: Payment
... custom fields here
)
So, rather than duplicating all the user and payment form fields per shop cart model, I've consolidated as above and implemented nested forms.
Now, the problem occurs at the template layer where there is to-date no generics support.
The parent/container form pulls in nested child forms like so:
#(_form: Form[Conference])
#user.nested( UserForm.form.fill(_form.get.user) )
#payment.nested( PaymentForm.form.fill(_form.get.payment) )
and then the child User form looks like:
#(_form: Form[User])
#inputText(_form("user.firstName"), '_label-> "First Name*", 'class-> "required")
#inputText(_form("user.lastName"), '_label-> "Last Name*", 'class-> "required")
...
and the User model:
case class User(firstName: String, lastName: String ...)
How do I access "user.firstName", "user.lastName", etc. when there is no user property in the User model? The Play Form apply method is:
def apply(key: String): Field = Field(
this,
key,
constraints.get(key).getOrElse(Nil),
formats.get(key),
errors.collect { case e if e.key == key => e },
data.get(key))
Basically it's going to look for property data.user.firstName which is obviously not going to work.
I've thought about adding a user property to User model:
case class User(firstName: String, lastName: String ...) {
val user: User
}
but not sure if that will work and/or wreak havoc with case class companion object apply/unapply.
At any rate, given the lack of generics, what's a viable solution to the problem?
Were generics supported we could pass in an upper bound and everything would be rosy:
trait CartOrder {
user: User,
payment: Payment
}
case class Conference(...) extends CartOrder
and then nested User form is passed an instance that contains a user property and we're good
#[T <: CartOrder](_form: Form[T])
#inputText(_form("user.firstName"), '_label-> "First Name*", 'class-> "required")
...
If type safety isn't a concern (Forms aren't all that type safe to begin with), you can just use Form[_] as the parameter type for your nested templates.
If you do want type safety, one option is to make a wrapper class for Form that is covariant and use that in place of Form. One implementation is:
package views.html
import play.api.data._
import play.api.libs.json.JsValue
object FormView {
implicit def formToFormView[A, T >: A](form: Form[A]): FormView[T] = new FormView[T] {
type F = A
def realForm = form
}
}
trait FormView[+T] {
type F <: T
def realForm: Form[F]
def apply(key: String): Field = realForm(key)
def constraints : Map[String, Seq[(String, Seq[Any])]] = realForm.constraints
def data: Map[String, String] = realForm.data
def error(key: String): Option[FormError] = realForm.error(key)
def errors(key: String): Seq[FormError] = realForm.errors(key)
def errors: Seq[FormError] = realForm.errors
def errorsAsJson: JsValue = realForm.errorsAsJson
def get: T = realForm.get
def formats: Map[String, (String, Seq[Any])] = realForm.formats
def globalError: Option[FormError] = realForm.globalError
def globalErrors: Seq[FormError] = realForm.globalErrors
def hasErrors: Boolean = realForm.hasErrors
def hasGlobalErrors: Boolean = realForm.hasGlobalErrors
override def hashCode: Int = realForm.hashCode
def mapping: Mapping[F] = realForm.mapping
def value: Option[T] = realForm.value
}
Now instead of your templates being
#(_form: Form[CartOrder])
which won't work because of invariance, you can use
#(_form: FormView[CartOrder])
and you can simply pass in any Form[T] where T is a subtype of CartOrder like
#user.nested(_form)
the implicits will handle the conversion from Form to FormView
A complete example can be found at: https://github.com/thatsmydoing/formtest
Ok, the dilly-o follows thusly (chime in if you have a better way):
play.api.data.Form[T] is invariant, so no dice passing in the super type of Conference (i.e. CartOrder) to the User form. In other words, this blows up:
// user.scala.html
#(_form: Form[CartOrder])
Basically you have to pass in an instance that itself is Form mappable.
To workaround the template layer fun house, I have implemented the following hack:
case class CartModel(user: User, payment: Payment)
EDIT
gets better, added in a bind helper to CartForm mapper below, which makes for clean syntax in views
object CartForm {
import play.api.data.Forms._
val form =
Form( mapping(
'user -> UserForm.mapper,
'payment -> PaymentForm.mapper )(CartModel.apply)(CartModel.unapply) )
// hey, generics!
def bind[T <: Form[_ <: CartContract]](t: T) =
t.value map{x=> form.fill( CartModel(x.user, x.payment) )} getOrElse form
}
and then in the Conference parent form, pull in User form fields like so:
#user.nested( CartForm.bind(_form) )
and the User form then receives:
#(_form: Form[CartModel])
A good deal of boilerplate is eliminated with nested forms in general, so overall, progress. Would be great not to rely on the intermediary Form mapper, but this is as good as I can come up with for now...
Guess you want
#(_form: Form[_ <: CartOrder])
instead of suggested
#[T <: CartOrder](_form: Form[T])