I’m trying to serve public and private assets via a Play Assets controller but my authentication annotations do not get executed on the Asset request methods that need it. When I specify a route to call secureAt, it doesn’t execute the annotation. Does Play support annotations for Asset controller requests?
I think the problem here is a mix of the Java and Scala APIs. Annotations are supported on Java API action methods (which return Result) but not on Scala API Actions. The Assets class uses Scala Actions.
A fix here will provide some custom coding to link the two APIs together. Sorry I only have time for a quick answer. Maybe someone else will be able to help…
So if I converted these to Java API action methods would I lose any of the benefits (caching, etc) from using the Assets controller for serving assets?
To me it makes sense to use the Assets controller for handling assets but I only want authenticated users to be able to view certain assets. If I were to convert all assets from being served by the Assets controller to a Java API controller what would an example method look like since I would need to convert a Scala action returned by at to a Result.
@Authenticated
public Result secureAt(String path, String file) {
boolean aggressiveCaching = true;
return assets.at(commonPrefix + path, file, aggressiveCaching);
}
I can’t see why it isn’t working. How are your conf/routes files? Also, how are the views generating URLs to public and private assets? Keep in mind that, if you having the following structure:
public
|_ private/asset.jpg
|_ a.public.asset.jpg
And a route like:
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
Then the following URL will deliver private/asset.jpg: http://localhost:9000/assets/private/asset.jpg.What you may want to do is to have segregate public and private assets at directory level too, so that you can later segregate them at routes level.
Oh, well it IS serving the file. It’s just not requiring authentication to access it. The annotation is not getting executed. It completely ignores it.
My routes file looks like
GET /docs/specification controllers.common.CommonAssets.secureAt(path="/private/specification", file = "swagger.json")
My asset structure is like:
common
|- app
|---controllers
|---assets
|-----private
|--------specification
|----------swagger.json
|- conf
I don’t have a GET /assets/*file route in my route file because I don’t need it. I also don’t have any views that generate URLs. I’m directly navigating to http://localhost:9000/docs/specification and it serves the file just fine, but it should be failing authentication.
Hum, so, I think that @richdougherty suspicions may be correct here. Mixing Java and Scala APIs (returning Action<AnyContent> and using an annotation) is probably not working as you expect.
No, all the headers added by Assets.at method will be preserved. But remember to return a play.mvc.Result (Java) instead of a play.api.mvc.Result (Scala). So your code would be something like:
Except that the asJava() method in you example returns an play.mvc.EssentialAction and not a play.mvc.Result, so this doesn’t work. Is there a underlying method I can call to pass in an Action or EssentialAction that will return a play.mvc.Result?
How much work would it be to add support in Play for annotations to get executed for AssetBuilder requests? Or allow assets to be served from Controller classes? It seems like as long as my controller action methods return a Result or CompletionStage<Result>, it executes the annotations correctly, but not when it returns a Action<> or EssentialAction.
I would be willing to contribute to support this but I would need some pointers on where I should start looking to fix this.
So, basically, run the Scala action to get its accumulator, materialize it (with a Materializer injected into your controller), and return the resulting Result. This is assuming there is no request body to process.
It would probably require some significant code changes, depending on how closely you wanted to mimic regular Java actions. The Scala API actions work at a lower level of abstraction than the Java actions. Java actions will assume an existing Http.Context, for example.
If you’re interested in how it works: there is a special instance of play.api.mvc.Action called JavaAction that wraps Java methods and does the necessary reflection to apply the annotation logic. This action is actually created by an instance of HandlerInvokerFactory provided implicitly based on the type of the route in the generated router.
Thanks a lot Greg. This actually worked. I feel like this is something that more and more people will utilize if they have a mix of public and private assets that they want only an authenticated client to see. Would love to see something more elegant get added to the Play framework, but in the mean time this will do for now. Thanks for the help.