Hi,
Is there any way to restrict the access of the compressed images to non-authenticated users using this bundle?, all the processed images go to a public folder in all the examples that I found in the web.
Anyone have a workaround?
Thanks in advance for the help
Greetings
I'm not 100% clear as to the behavior you would like. _If the intention is to allow access to the transformed images only to authenticated users, and simply deny such to non-authenticated users at the HTTP-level_, then Symfony's security component allows you to perform these operations through a firewall definition.
On the other hand, _if the intention is to always serve an image to all users, but selectively decide whether it is the original version or the transformed version_, then there is no native functionality within this bundle to achieve that, but implementing it on top of this codebase should be fairly straight forward by extending the desired component's class and performing the desired conditional in your overridden method prior to invoking the method's parent implementation (among other, alternate, possible routes).
If the latter is what you are looking to accomplish and you need some insight into where to begin, give a shout; I don't have the time to provide any examples in the immediate, but will be happy to later tonight or tomorrow morning when I'm not running out the door. ;-)
Hi @robfrawley,
Thanks for the answer :+1:
I'm going to build a social network, I want to compress the user photos to improve the site performance, but also want to keep the more private possible, like:
1 - if you're not my friend, you can see my photos
2 - If you aren't logged you can see any photos
By default this bundle store the compressed images in the public folder this folder is always visible, with the symfony security component, I think that we can achieve the point 2, not sure if I can achieve the 1
@prodriguezval Was one of those two points supposed to contain a "can't" in there instead of a "can"?
@robfrawley yes, the point 1,
I don't know if this can be achieved with this bundle.
This is my particular doubt
Thanks
@prodriguezval It absolutely can! ;-)
A fairly straight-forward way to handle this would be overwriting the default imagine controller with our own implementation and handling the required conditions there.
For this example, I'm we are using two additional, custom services whos logic you need to define yourself (all I describe is the used method signatures called within our custom controller.
AppBundle\Component\Inspector\SocialInspector:
class SocialInspector
{
public function isFriendsWith(User $user, User ...$friends)
{
foreach ($friends as $f) {
if (!$user->hasFriend($f)) {
return false;
}
}
return true;
}
AppBundle\Component\Inspector\MediaInspector:
class MediaInspector
{
public function getOwnerOf($media)
{
// determine owner of $media
// $user =
return $user;
}
}
So, to recap, we'll be using these two methods, whose classes will be defined in the service container, within our new controller.
SocialInspector::areFriendsOf - Checks if any number of users are friends (a variadic function such as this requires PHP 5.6+ so refactor to explicitly defined parameters if lower PHP version required).MediaInspector::getOwnerFor - Returns a User object instance for the owner of the passed media resource.Let's get started by writing a controller that extends this bundle's ImagineController implementation.
AppBundle/Controller/ImagineController:
<?php
namespace AppBundle\Controller;
use AppBundle\Component\Inspector\MediaInspector;
use AppBundle\Component\Inspector\SocialInspector;
use Liip\ImagineBundle\Controller\ImagineController as BaseImagineController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class ImagineController extends BaseImagineController
{
private $tokenStorage;
private $authorizationChecker;
private $inspectorMedia;
private $inspectorSocial;
public function setTokenStorage(TokenStorage $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function setAuthorizationChecker(AuthorizationChecker $authorizationChecker)
{
$this->authorizationChecker = $authorizationChecker;
}
public function setInspectorMedia(InspectorMedia $inspectorMedia)
{
$this->inspectorMedia = $inspectorMedia;
}
public function setInspectorSocial(SocialInspector $inspectorSocial)
{
$this->inspectorSocial = $inspectorSocial;
}
public function filterAction(Request $request, $path, $filter)
{
$this->assertAllowableResourceForUser($path);
return parent::filterAction($request, $path, $filter);
}
public function filterRuntimeAction(Request $request, $hash, $path, $filter)
{
$this->assertAllowableResourceForUser($path));
return parent::filterRuntimeAction($request, $hash, $path, $filter);
}
private function assertAllowableResourceForUser($image)
{
if ($this->authorizationChecker->isGranted('IS_AUTHENTICATED_ANONYMOUSLY')) {
// do something special for anonymous users?
throw new AccessDeniedException('Anonymous users cannot view images...');
// or perhaps just return to stop checks and allow image for this case
return;
}
if (!$user = $this->getUser()) {
// do something special if no user logged in (anonymous)?
throw new AccessDeniedException('Unable to determine active user...');
// or perhaps just return to stop checks and allow image for this case
return;
}
if (!$owner = $this->inspectorMedia->getOwnerFor($image)) {
// do something special if media owner cannot be determined?
throw new AccessDeniedException(sprintf('Unable to determine media owner user for "%s"...', $image);
// or perhaps just return to stop checks and allow image for this case
return;
}
if (!$this->inspectorSocial->isFriendsWith($user, $owner)) {
// do something special if owner and active user are not friends?
throw new AccessDeniedException('Cannot view photo of user outside of your friend network...');
}
// allow image
}
private function getUser()
{
if (!$token = $this->tokenStorage->getToken()) {
return false;
}
if (!$user = $token->getUser()) {
return false;
}
return $user;
}
}
Lastly, let's register our new controller and helper services in the Symfony service container.
app/config/services.yml:
parameters:
liip_imagine.controller.class: AppBundle\Controller\ImagineController
services:
liip_imagine.controller:
class: "%liip_imagine.controller.class%"
arguments:
- "@liip_imagine.data.manager"
- "@liip_imagine.filter.manager"
- "@liip_imagine.cache.manager"
- "@liip_imagine.cache.signer"
- "@liip_imagine.data.manager"
- "?@logger"
calls:
- [setTokenStorage, ["@security.token_storage"]]
- [setAuthorizationChecker, ["@security.authorization_checker"]]
- [setInspectorMedia, ["@inspector.media"]]
- [setInspectorSocial, ["@inspector.social"]]
inpsector.social:
class: AppBundle\Component\Inspector\SocialInspector
arguments:
# you'll need some other services passed here, likely
inspector.media:
class: AppBundle\Component\Inspector\InspectorMedia
arguments:
# you'll need some other services passed here, likely
So... perhaps something like that would solve your use-case?
This approach is kind of "high-level" and doesn't provide much in the sense of integration with the various conditions _within_ your imagine bundle config. You could also accomplish similar behavior using a combination of custom data loaders, cache resolvers, and other component layers within this bundle.
Hi @robfrawley,
I'll implement and test your solution, very nice explanation
Thank you so much!
Is there an update version of this example ? This doesn't work anymore
Most helpful comment
@prodriguezval It absolutely can! ;-)
A fairly straight-forward way to handle this would be overwriting the default imagine controller with our own implementation and handling the required conditions there.
For this example, I'm we are using two additional, custom services whos logic you need to define yourself (all I describe is the used method signatures called within our custom controller.
AppBundle\Component\Inspector\SocialInspector:
AppBundle\Component\Inspector\MediaInspector:
So, to recap, we'll be using these two methods, whose classes will be defined in the service container, within our new controller.
SocialInspector::areFriendsOf- Checks if any number of users are friends (a variadic function such as this requires PHP 5.6+ so refactor to explicitly defined parameters if lower PHP version required).MediaInspector::getOwnerFor- Returns aUserobject instance for the owner of the passed media resource.Let's get started by writing a controller that extends this bundle's
ImagineControllerimplementation.AppBundle/Controller/ImagineController:
Lastly, let's register our new controller and helper services in the Symfony service container.
app/config/services.yml:
So... perhaps something like that would solve your use-case?
This approach is kind of "high-level" and doesn't provide much in the sense of integration with the various conditions _within_ your imagine bundle config. You could also accomplish similar behavior using a combination of custom data loaders, cache resolvers, and other component layers within this bundle.