EasyAdmin list action for current user - symfony

I'm trying to retrieve a list of objects from database where Entity.user = "current user".
It's a ManyToOne relation between an Entity entity and a User Entity
I tried to use the dql-filter option in the bundle config but couldn't find a parameter variable like we can find in Controller $this->getUser() or in Twig {{app.user}}
I tried to use custom controller but I am confused as the documentation is not very detailed.

I would go with a custom controller and overwrite findAll or createListQueryBuilder by always adding a DQL-filter. Something a bit like this:
protected function createListQueryBuilder($entityClass, $sortDirection, $sortField = null, $dqlFilter = null)
{
if (null === $dqlFilter) {
$dqlFilter = sprintf('entity.user = %s', $this->getUser()->getId());
} else {
$dqlFilter .= sprintf(' AND entity.user = %s', $this->getUser()->getId());
}
return parent::createListQueryBuilder($entityClass, $sortDirection, $sortField, $dqlFilter);
}

Related

ParamConverter: inject curent user in Repository

In a view, I want to display linked values, but all of the linked values can't be displayed because they depends to the user access.
To do that I just need to do a leftJoin with a ->where('user', $user). The question is... how can I inject the current user in the Repository from the ParamConverter ?
Assuming you are using Doctrine, this should work;
In your controller;
$objRepo = $this->getDoctrine()->getManager()->getRepository('AppBundle:Objects');
$files = $this->objRepo->getAllForUserId($this->getUser()->getId());
And in your repo file;
public function getAllForUserId($user_id, $limit=100)
{
if (null === $user_id) {
throw new ORMInvalidArgumentException('User id not set');
}
$queryBuilder = $this->createQueryBuilder('obj');
$queryBuilder->select(array('obj', 'usr'))
->innerJoin('obj.users', 'usr')
->where('usr.id = :user_id')
->setParameter(':user_id', $user_id)
->orderBy('file.created', Criteria::DESC);
$query = $queryBuilder->getQuery();
return $query->getResult();
}

Return entity record value based on custom value in Symfony2

I'm making an API and I need to display data from entity based on action type. For example, I have User and his visibility preferences (to hide/show his name for other people). Doing this like that:
<?php
// entity
public function getSurname()
{
$visibility = $this->getVisibility();
if($visibility['name'] == 0)
return $this->surname;
return '';
}
is ok, but if User is logged in, I want to show him his name, for example, in edit account.
The best way I think is to edit record when I get it from database, but how to this on doctrine object?
<?php
//controller
$user = $this->getDoctrine()->getRepository('AcmeDemoBundle:User')->findOneById($id);
$user = $this->getVisibility();
if($user != $this->getUser() && $visibility['name'] == 0)
$user->setSurname(''); //but this save this to DB, not to "view"
UPDATE
Unfortunately (or I'm doing something wrong) my problem can't be solved by Snake answer, beause when I do this code:
<?php
$user = $this->getDoctrine()->getRepository('AcmeDemoBundle')-findOneById($id);
return array(
self::USER => $user
);
In my API response, entity modifications don't work, because I think this is getting record directly from DB? And I need return whole object like in code above.
UPDATE2
I found workaround for this
<?php
// entity
/**
* #ORM\PostLoad
*/
public function postLoad() {
$this->surname = $this->getSurname();
}
and then I can just return full $user object
If you want to show the surname depends of visibility, you can add the Symfony\Component\Security\Core\User\EquatableInterface and edit your function:
// entity
public function getSurname(Acme\DemoBundle\User $user = null)
{
// Nothing to compare or is the owner
if( !is_null( $user ) && $this->isEqualTo($user) ){
return $this->surname;
}
// else...
$visibility = $this->getVisibility();
if($visibility['name'] == 0)
return $this->surname;
return '';
}
After in your controller you only must get the surname:
//controller
$user = $this->getDoctrine()->getRepository('AcmeDemoBundle:User')->findOneById($id);
// If the user is the owner, show the surname, otherwise it shows the surname depends of visibility
$surname = $user->getSurname( $this->getUser() );
Also, you can execute the logic in the controller (check if is the same user and get the visibility...).
I suggest you read about ACL too.

Laravel Eloquent hasMany returns null

Laravel 4 is giving me unexpected results on a new project. To try and help me better understand the results I tried what I thought would be a simple exercise and use a friends old WordPress blog on his web hosting.
Post model:
class Post extends Eloquent {
protected $table = 'wp_posts';
public function meta()
{
return $this->hasMany('Meta', 'post_id');
}
}
Meta model
class Meta extends Eloquent {
protected $table = 'wp_postmeta';
public function post()
{
return $this->belongsTo('Post');
}
}
I have tried all of these variations with no avail...
TestController:
public function get_index()
{
// $test = Post::with('Meta')->get();
// $test = Post::with('Meta')->where('id', '=', '219')->first();
// $test = Post::find(219)->Meta()->where('post_id', '=', '219')->first();
// $test = Post::find($id)->Meta()->get();
// $test = Meta::with('Post')->get();
$id = 219;
$test = Post::find($id)->meta;
return $test;
}
returned in the event listener:
string(47) "select * from `wp_posts` where `id` = ? limit 1" string(65) "select * from `wp_postmeta` where `wp_postmeta`.`post_id` is null" []
Please tell me I am just overlooking something really minor and stupid and I just need some sleep.
Though is that while the SQL is case-insensitive, the array of attributes the model gets populated with is a PHP array where indexes are case-sensitive. The schema had the primary key as ID

unique slugs with multi entities in symfony2

I have a small symfony2 application where a user can create pages. Each page should be accessible via the route /{user_slug}/{page_slug}. I have the entities user and page and I use the sluggable behavior for both entities. In order to find the correct page the combination of user_slug and page_slug has to be unique.
What is the best way to check that the combination of user_slug and page_slug is uniqe?
Try this in your prepository:
public function findByUsernameAndSlug($username, $slug)
{
$em = $this->getEntityManager();
$query = $em->createQuery("
SELECT g
FROM Acme\PagesBundle\Entity\Page p
JOIN p.owner u
WHERE u.username = :username
AND p.slug = :slug
")
->setParameter('username', $username)
->setParameter('slug', $slug);
foreach ($query->getResult() as $goal) {
return $goal;
}
return null;
}
Before you persist the entity in your service-layer check whether given combination of user and page slug are unique, if not modify the page slug (append -2 or something like that) or throw an exception:
public function persistPage(Page $page) {
$userSlug = $page->getUser()->getSlug();
$pageSlug = $page->getSlug();
if ($this->pagesRepository->findOneBySlugs($userSlug, $pageSlug) != null) {
// given combination already exists
throw new NonUniqueNameException(..);
// or modify the slug
$page->setSlug($page->getSlug() . '-2');
return $this->persistPage($page);
}
return $this->em->persist($page);
}
// PagesRepository::findOneBySlugs($userSlug, $pageSlug)
public function findOneBySlugs($userSlug, $pageSlug) {
$query = $this->em->createQueryBuilder('p')
->addSelect('u')
->join('p.user', 'u')
->where('p.slug = :pageSlug')
->where('u.slug = :userSlug;)
->getQuery();
$query->setParameters(combine('userSlug', 'pageSlug'));
return $query->getSingleResult();
}

How to use class-scope aces in Symfony2?

I've got a problem with class-scope aces. I've created an ace for a
class like this :
$userIdentity = UserSecurityIdentity::fromAccount($user);
$classIdentity = new ObjectIdentity('some_identifier', 'Class\FQCN');
$acl = $aclProvider->createAcl($classIdentity);
$acl->insertClassAce($userIdentity, MaskBuilder::MASK_CREATE);
$aclProvider->updateAcl($acl);
Now, I'm trying to check the user's permissions. I've found this way
of doing things, which is not documented, but gives the expected
results on a class basis :
$securityContext->isGranted('CREATE', $classIdentity); // returns true
$securityContext->isGranted('VIEW', $classIdentity); // returns true
$securityContext->isGranted('DELETE', $classIdentity); // returns false
This method is well adapated to the "CREATE" permission check, where
there's no available object instance to pass to the method. However,
it should be possible to check if another permission is granted on a
particular instance basis :
$entity = new Class\FQCN();
$em->persist($entity);
$em->flush();
$securityContext->isGranted('VIEW', $entity); // returns false
This is where the test fails. I expected that an user who has a given
permission mask on a class would have the same permissions on every
instance of that class, as stated in the documentation ("The
PermissionGrantingStrategy first checks all your object-scope ACEs if
none is applicable, the class-scope ACEs will be checked"), but it
seems not to be the case here.
you are doing it right. and according to the bottom of this page, it should work, but it does not.
the easiest way to make it work is creating an AclVoter class:
namespace Core\Security\Acl\Voter;
use JMS\SecurityExtraBundle\Security\Acl\Voter\AclVoter as BaseAclVoter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Doctrine\Common\Util\ClassUtils;
class AclVoter extends BaseAclVoter
{
public function vote( TokenInterface $token , $object , array $attributes )
{
//vote for object first
$objectVote = parent::vote( $token , $object , $attributes );
if( self::ACCESS_GRANTED === $objectVote )
{
return self::ACCESS_GRANTED;
}
else
{
//then for object's class
$oid = new ObjectIdentity( 'class' , ClassUtils::getRealClass( get_class( $object ) ) );
$classVote = parent::vote( $token , $oid , $attributes );
if( self::ACCESS_ABSTAIN === $objectVote )
{
if( self::ACCESS_ABSTAIN === $classVote )
{
return self::ACCESS_ABSTAIN;
}
else
{
return $classVote;
}
}
else if( self::ACCESS_DENIED === $objectVote )
{
if( self::ACCESS_ABSTAIN === $classVote )
{
return self::ACCESS_DENIED;
}
else
{
return $classVote;
}
}
}
return self::ACCESS_ABSTAIN;
}
}
then in security.yml set this:
jms_security_extra:
voters:
disable_acl: true
and finally set up the voter as a service:
core.security.acl.voter.basic_permissions:
class: Core\Security\Acl\Voter\AclVoter
public: false
arguments:
- '#security.acl.provider'
- '#security.acl.object_identity_retrieval_strategy'
- '#security.acl.security_identity_retrieval_strategy'
- '#security.acl.permission.map'
- '#?logger'
tags:
- { name: security.voter , priority: 255 }
- { name: monolog.logger , channel: security }
You need to ensure each object has its own ACL (use $aclProvider->createAcl($entity)) for class-scope permissions to work correctly.
See this discussion: https://groups.google.com/forum/?fromgroups=#!topic/symfony2/pGIs0UuYKX4
If you don't have an existing entity, you can check against the objectIdentity you created.
Be careful to use "double-backslashes", because of the escaping of the backslash.
$post = $postRepository->findOneBy(array('id' => 1));
$securityContext = $this->get('security.context');
$objectIdentity = new ObjectIdentity('class', 'Liip\\TestBundle\\Entity\\Post');
// check for edit access
if (true === $securityContext->isGranted('EDIT', $objectIdentity)) {
echo "Edit Access granted to: <br/><br/> ";
print_r("<pre>");
print_r($post);
print_r("</pre>");
} else {
throw new AccessDeniedException();
}
That should work!
If you would check for "object-scope" you could just use $post instead of $objectIdentity in the isGranted function call.
I have tried to find the best solution for this problem and I think the best answer is the one of rryter / edited by Bart. I just want to extend the solution.
Let's say you want to give access to a specific object type for a specific user, but not for a concrete object instance (for example id=1).
Then you can do the following:
$aclProvider = $this->get('security.acl.provider');
$objectIdentity = new ObjectIdentity('class', 'someNamspace\\SeperatedByDoubleSlashes');
$acl = $aclProvider->createAcl($objectIdentity);
// retrieving the security identity of the currently logged-in user
$securityContext = $this->get('security.context');
$user = $securityContext->getToken()->getUser();
$securityIdentity = UserSecurityIdentity::fromAccount($user);
// grant owner access
$acl->insertObjectAce($securityIdentity, MaskBuilder::MASK_OWNER);
$aclProvider->updateAcl($acl);
$securityContext = $this->get('security.context');
// check for edit access
if (false === $securityContext->isGranted('EDIT', $objectIdentity)) {
throw new AccessDeniedException();
}
The difference to the example given by the symfony cookbook is that you are using the class scope and not the object scope.
There's only 1 line that makes the difference:
$objectIdentity = new ObjectIdentity('class', 'someNamspace\\SeperatedByDoubleSlashes');
instead of:
$objectIdentity = ObjectIdentity::fromDomainObject($object);
You can still add specific permissions for specific object instances if you have at least one class scope permission in your classes acl.

Resources