I've added keycloak authentication to my Playframework project, but it's implemented only for one Action. I'm trying to create a custom Action class which will be validating the JWT and which I'll inject into all classes.
My current endpoint code:
val configData: Config = config.get[Config]("auth.keycloak")
val keycloakConfig = KeycloakConfig(configData)
val kcContext = KeycloakHelperContext(keycloakConfig)
def getUserProps() = Action {
implicit request: Request[AnyContent] =>
val user = kcContext.dpKcAuthentication.validateJWT
Ok(write(Map("user" → user))).as(JSON)
}
The custom actionimpl I'm trying to create:
val configData: Config = config.get[Config]("auth.keycloak")
val keycloakConfig = KeycloakConfig(configData)
val kcContext = KeycloakHelperContext(keycloakConfig)
override def invokeBlock[A](request: Request[AnyContent], block: Request[A] => Future[Result]): Future[Result] = {
val user = kcContext.dpKcAuthentication.validateJWT(request)
block(request)
}
And it says:
Method 'invokeBlock' overrides nothing
It happens when I pass request to validateJWT method. I don't do it in my controller because I simply say that the request is implicit and it's passed there.
For current implementation, I need to make the request inside invokeBlock implicit or find a way to pass it validateJWT method.
Related
I try to write a unit test for a small function in a controller using Play/ScalaTest+ Play. The function I want to test looks like this:
def functionToTest(id: String) = Action.async {
implicit request =>
lang
deeperFunction{ implicit context =>
...
}
}
The deeperFunction
def deeperFunction(block: Context => Future[Result])(implicit request: RequestHeader): Future[Result] = {
// returns Future.successful(Found("DummyUrltoRedirect"))
}
}
The deeperFunction is inherited from a trait and I don't want use the real one here because it's a unit test and so I want to use a matcher instead
val deeperMock = mock[Rainmaker]
val contextMock = mock[Context]
val controller = new Controller()(.....) // list of implicit arguments
"Controller" must {
"return something" in {
val request = FakeRequest("GET", "/something")
when(deeperMock.deeperFunction(anyObject)(anyObject)) thenReturn Future.successful(Found("DummyUrlToRedirect"))
val id = "id"
val result = controller.functionToTest(id).apply(request)
status(result) mustBe Ok
}
}
But when I run this, the line "val result = controller.functionToTest(id).apply(request)" still seems to call the real deeperFunction, not the fake one and therefore throws a null matcher at some point.
I also tried to use
when(controller.deeperFunction(anyObject)(anyObject)) thenReturn Future.successful(Found("DummyUrlToRedirect"))
instead, because the deeperFunction is inherited, but with the same result.
I tried to stick to theses instructions
ScalaTest+Play
dzone
but it seems I am still missing some basics/understanding. Thanks in advance.
In the Play documentation the Request type is generic [A] for action composition functions.
I am trying to add a custom query object to every endpoint that is responsible for querying data, to do that I need to grab the request body as JsValue and parse it into an appropriate case class. I cant seem to add a type to the WrappedRequest or the refine method in the action, and I cant do request.body.as[JsValue] in the refine method. Is there a way accomplish the following?
I would like to do something like this with action composition in endpoints that handle querying data:
def SomeEndpointWithAQuery() = auth.protectedAction(parse.json).andThen(QueryAction)) { request:QueryRequest[JsValue] =>
val query = request.queryObject
res = // Do query stuff
Ok(res)
}
QueryRequest is a WrappedRequest that provides a query object:
class QueryRequest[A](val query: QueryParser, request: AuthUserRequest[A]) extends WrappedRequest[A](request) {
def queryObject = query.parseQuery()
}
object QueryActionObject {
def QueryAction()(implicit ec: ExecutionContext): ActionRefiner[AuthUserRequest, QueryRequest] = new ActionRefiner[AuthUserRequest, QueryRequest] {
def executionContext: ExecutionContext = ec
def refine[A](input: AuthUserRequest[A]) = Future.successful {
val qp = new QueryParser(input.body.as[JsObject])
if (qp.validate()) {
Right(new QueryRequest(qp, input))
} else {
// bad request handling ...
}
}
}
}
auth.protectedAction is an actionBuilder that adds user info based on request.session info, it shouldnt affect how this queryAction works.
My app hits many different dbs, which db depends on a query string parameter. I have a DatabaseConfigLocator that takes the string and returns the config and it works great. My problem is I want to make the config for each request available implicitly in my controller. I have tried two approaches.
class MyController extends Controller{
implicit def dbConfig(implicit request: RequestHeader): DatabaseConfig[JdbcProfile] = DatabaseConfigLocator.get[JdbcProfile](request.getQueryString("dbName")
}
This dosn't compile unless I change it to an implicit val with the same type but I need to recheck the query string every request not just once so I don't think an implicit val will work
The other approach was to create an action
object IODBAction extends ActionBuilder[Request]{
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
implicit def dbConfig(implicit request: RequestHeader): DatabaseConfig[JdbcProfile] = DatabaseConfigLocator.get[JdbcProfile]("blah")
block(request)
}
}
but that implicit is not available in the scope of the block and I cant find any way to pass it in as an implicit.
My goal is to be able to do something like this
class MyController extends Controller {
def create = {
Action.async {
request =>
ApiResponse {
for {
id <- aProvider.save(validRegistrationRequest.toVisitor)
} yield id
}
}
}
}
class aProvider {
def save(v: Visitor)(implicit dbConfig: DatabaseConfig[JdbcProfile]): ApiResponse[VisitorId]
}
or if the provider can get the implicit at instantiation time all the better
class aProvider(implicit dbConfig: DatabaseConfig[JdbcPRofile]) {
def save(v: Visitor): ApiResponse[VisitorId]
}
Any advice on how to approach this or if its possible with play framework?
Unfortunately, I fear you're stuck with an action receiving a single parameter, so you need to stick to the standard play "Action Composition" pattern. This is documented pretty extensively in the play docs.
I'd define yourself a a "Context" like this:
case class Context(dbConfig: DatabaseConfig[JDBCProfile], request: Request[A])
extends WrappedRequest(request)
Then create a custom action builder like this:
object DBIOAction extends ActionBuilder[Context]{
def invokeBlock[A](request: Request[A], block: (Context[A]) => Future[Result]) = {
val dbConfig = DatabaseConfigLocator.get[JdbcProfile]("blah")
val context = Context(dbConfig, request)
block(context)
}
}
You then should be able to use it like this:
def index = DBIOAction { implicit context =>
// do some stuff. return a result
}
In order to make things simple, I'd pass the implicit context into your service methods, perhaps extracting the dbConfig from the context and passing it the rest of the way down to your DAO's.
class FunService {
def getSomeData(param1: String)(implicit context: Context) = {
// do some work, perhaps using context.dbConfig
}
I've implemented authorization for my Play Framework (version 2.3.5) application as per the official security documentation:
trait Secured {
def username(request: RequestHeader) = request.session.get(Security.username)
def onUnauthorized(request: RequestHeader) = Results.Redirect(routes.Login.index)
def withAuth(f: => String => Request[AnyContent] => Result) = {
Security.Authenticated(username, onUnauthorized) { user =>
Action(request => f(user)(request))
}
}
}
However, when I decorate my controller actions with the withAuth function like so:
def export = withAuth { username => implicit request =>
val csv = csv()
Ok(csv)
.as("text/csv")
.withHeaders(
CONTENT_DISPOSITION -> ("attachment; filename=export.csv"),
CONTENT_LENGTH -> csv.length.toString
)
}
I get compilation errors in my controller specs2 unit tests:
"export data as CSV" in {
val controller = new controllers.DataController
val result = controller.export().apply(FakeRequest())
headers(result) must havePair("Content-Disposition" -> ("attachment; filename=export.csv"))
}
The call to the headers test helper function fails with the following compiler error message:
type mismatch; found : play.api.libs.iteratee.Iteratee[Array[Byte],play.api.mvc.Result] required: scala.concurrent.Future[play.api.mvc.Result]
Am I doing something wrong? I've tried some of the remedies suggested by other Stackoverflow users but they all seem to rely on setting the body type, but my actions do not have a body type.
It looks as though the call to withAuth obscures some of the types of the Action being wrapped so maybe there is a more type-safe way of expressing the Secured trait?
Try changing this:
val result = controller.export().apply(FakeRequest())
into this:
val result = controller.export().apply(FakeRequest()).run
And then it should work.
I am trying to create a custom play.api.mvc.Action which can be used to populate a CustomerAccount based on the request and pass the CustomerAccount into the controller.
Following the documentation for Play 2.2.x I've created an Action and ActionBuilder but I cannot seem to return the CustomerAccount from within the action.
My current code is:
case class AccountWrappedRequest[A](account: CustomerAccount, request: Request[A]) extends WrappedRequest[A](request)
case class Account[A](action: Action[A]) extends Action[A] {
lazy val parser = action.parser
def apply(request: Request[A]): Future[SimpleResult] = {
AccountService.getBySubdomain(request.host).map { account =>
// Do something to return the account like return a new AccountWrappedRequest?
action(AccountWrappedRequest(account, request))
} getOrElse {
Future.successful(NotFound(views.html.account_not_found()))
}
}
}
object AccountAction extends ActionBuilder[AccountWrappedRequest] {
def invokeBlock[A](request: Request[A], block: (AccountWrappedRequest[A]) => Future[SimpleResult]) = {
// Or here to pass it to the next request?
block(request) // block(AccountWrappedRequest(account??, request))
}
override def composeAction[A](action: Action[A]) = Account(action)
}
Note: This will not compile because the block(request) function is expecting a type of AccountWrappedRequest which I cannot populate. It will compile when using a straight Request
Additionally...
Ultimately I want to be able to combine this Account action with an Authentication action so that the CustomerAccount can be passed into the Authentication action and user authentication can be provided based on that customer's account. I would then want to pass the customer account and user into the controller.
For example:
Account(Authenticated(Action))) { request => request.account; request.user ... } or better yet as individual objects not requiring a custom request object.
I'm not sure if this is the best way to do it but I have managed to come up with a solution that seems to work pretty well.
The key was to match on the request converting it into an AccountWrappedRequest inside invokeBlock before passing it on to the next request. If another Action in the chain is expecting a value from an earlier action in the chain you can then similarly match the request converting it into the type you need to access the request parameters.
Updating the example from the original question:
case class AccountWrappedRequest[A](account: CustomerAccount, request: Request[A]) extends WrappedRequest[A](request)
case class Account[A](action: Action[A]) extends Action[A] {
lazy val parser = action.parser
def apply(request: Request[A]): Future[SimpleResult] = {
AccountService.getBySubdomain(request.host).map { account =>
action(AccountWrappedRequest(account, request))
} getOrElse {
Future.successful(NotFound(views.html.account_not_found()))
}
}
}
object AccountAction extends ActionBuilder[AccountWrappedRequest] {
def invokeBlock[A](request: Request[A], block: (AccountWrappedRequest[A]) => Future[SimpleResult]) = {
request match {
case req: AccountRequest[A] => block(req)
case _ => Future.successful(BadRequest("400 Invalid Request"))
}
}
override def composeAction[A](action: Action[A]) = Account(action)
}
Then inside the apply() method of another Action (the Authenticated action in my case) you can similarly do:
def apply(request: Request[A]): Future[SimpleResult] = {
request match {
case req: AccountRequest[A] => {
// Do something that requires req.account
val user = User(1, "New User")
action(AuthenticatedWrappedRequest(req.account, user, request))
}
case _ => Future.successful(BadRequest("400 Invalid Request"))
}
}
And you can chain the actions together in the ActionBuilder
override def composeAction[A](action: Action[A]) = Account(Authenticated(action))
If AuthenticatedWrappedRequest is then passed into the controller you would have access to request.account, request.user and all the usual request parameters.
As you can see there are a couple of cases where the response is unknown which would generate a BadRequest. In reality these should never get called as far as I can tell but they are in there just incase.
I would love to have some feedback on this solution as I'm still fairly new to Scala and I'm not sure if there might be a better way to do it with the same result but I hope this is of use to someone too.
I wrote a standalone small (ish) example that does what you're looking for:
https://github.com/aellerton/play-login-example
I gave up trying to use the Security classes that exist in the play framework proper. I'm sure they're good, but I just couldn't understand them.
Brief guide...
In the example code, a controller is declared as using the AuthenticatedRequests trait:
object UserSpecificController extends Controller with AuthenticatedRequests {
...
}
Forcing any page to require authentication (or redirect to get it) is done with the RequireAuthentication action:
def authenticatedIndex = RequireAuthentication { implicit request: AuthenticatedRequest[AnyContent] =>
Ok("This content will be accessible only after logging in)
}
Signing out is done by using the AbandonAuthentication action:
def signOut = AbandonAuthentication { implicit request =>
Ok("You're logged out.").withNewSession
}
Note that for this to work, you must override methods from the AuthenticatedRequests trait, e.g.:
override def authenticationRequired[A](request: Request[A]): Future[SimpleResult] = {
Future.successful(
Redirect(routes.LoginController.showLoginForm).withSession("goto" -> request.path)
)
}
There's more to it; best to see the code.
HTH
Andrew