Skip to content

Instantly share code, notes, and snippets.

@gimler
Last active June 28, 2017 19:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gimler/5116843 to your computer and use it in GitHub Desktop.
Save gimler/5116843 to your computer and use it in GitHub Desktop.
<?php
namespace WSL\BaseBundle\Util\Helper;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Acl\Permission\MaskBuilder;
/**
* Description of ACL
*
* @author Anil
*/
class AclHelper
{
function __construct($doctrine, $securityContext)
{
$this->em = $doctrine->getManager();
$this->securityContext = $securityContext;
$this->aclConnection = $doctrine->getConnection('default');
}
protected function cloneQuery(Query $query)
{
$aclAppliedQuery = clone $query;
$aclAppliedQuery->setParameters($query->getParameters());
return $aclAppliedQuery;
}
/**
* This will clone the original query and
* @param QueryBuilder $queryBuilder
* @param array $permissions
* @return type
*/
public function apply(QueryBuilder $queryBuilder,
array $permissions = array("VIEW"))
{
$whereQueryParts = $queryBuilder->getDQLPart('where');
if (empty($whereQueryParts)) {
$fromQueryParts = $queryBuilder->getDQLPart('from');
$firstFromQueryAlias = $fromQueryParts[0]->getAlias();
$queryBuilder->where($firstFromQueryAlias . '.id > 0'); // this will help in cases where no where query is specified, where query is required to walk in where clause
}
$query = $this->cloneQuery($queryBuilder->getQuery());
$builder = new MaskBuilder();
foreach ($permissions as $permission) {
$mask = constant(get_class($builder) . '::MASK_' . strtoupper($permission));
$builder->add($mask);
}
$query->setHint('acl.mask', $builder->get());
//Change this to the place where you saved your AclWalker
$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'WSL\BaseBundle\Util\Doctrine\SqlWalker\AclWalker');
$entities = $queryBuilder->getRootEntities();
$query->setHint('acl.root.entities', $entities);
$query->setHint('acl.extra.query',
$this->getPermittedIdsACLSQLForUser($query, $queryBuilder));
$class = $this->em->getClassMetadata($entities[0]);
$entityRootTableName = $class->getQuotedTableName($this->em->getConnection()->getDatabasePlatform());
$entityRootAlias = $queryBuilder->getRootAlias();
$query->setHint('acl.entityRootTableName', $entityRootTableName);
$query->setHint('acl.entityRootTableDqlAlias', $entityRootAlias);
return $query;
}
/**
* This query works well with small offset, but if want to use it with large offsets please refer to the link on how to implement
* http://www.scribd.com/doc/14683263/Efficient-Pagination-Using-MySQL
* This will only check permissions on first enity added in the from clause, it will not check permissions
* By default the number of rows returned are 10 starting from 0
* @param Query $query
* @param QueryBuilder $queryBuilder
* @return String Sql
*/
private function getPermittedIdsACLSQLForUser(Query $query,
QueryBuilder $queryBuilder)
{
$database = $this->aclConnection->getDatabase();
$mask = $query->getHint('acl.mask');
$rootEntities = $query->getHint('acl.root.entities');
foreach ($rootEntities as $rootEntity) {
$rE[] = '"' . str_replace('\\', '\\\\', $rootEntity) . '"';
// For now ACL will be checked for first root entity, it will not check for all other entities in join etc..,
break;
}
$rootEntities = implode(',', $rE);
$token = $this->securityContext->getToken(); // for now lets imagine we will have token i.e user is logged in
$user = $token->getUser();
$identifier = "''";
if (is_object($user)) {
$identifiers = array();
$userRoles = $user->getRoles();
foreach ($userRoles as $role) {
// The reason we ignore this is because by default FOSUserBundle adds ROLE_USER for every user
if ($role !== 'ROLE_USER'){
$identifiers[] = $role;
}
}
$identifiers[] = str_replace('\\', '\\\\', get_class($user)) . '-' . $user->getUserName();
$identifier = '"' . implode('","', $identifiers) . '"';
}
$selectQuery = <<<SELECTQUERY
SELECT DISTINCT o.object_identifier as id FROM {$database}.acl_object_identities as o
INNER JOIN {$database}.acl_classes c ON c.id = o.class_id
LEFT JOIN {$database}.acl_entries e ON (
e.class_id = o.class_id AND (e.object_identity_id = o.id OR {$this->aclConnection->getDatabasePlatform()->getIsNullExpression('e.object_identity_id')})
)
LEFT JOIN {$database}.acl_security_identities s ON (
s.id = e.security_identity_id
)
WHERE c.class_type = {$rootEntities}
AND s.identifier IN ({$identifier})
AND e.mask >= {$mask}
SELECTQUERY;
return $selectQuery;
}
}
<?php
namespace WSL\BaseBundle\Util\Doctrine\SqlWalker;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\TreeWalkerAdapter;
use Doctrine\ORM\Query\AST\SelectStatement;
use Doctrine\ORM\Query\Exec\SingleSelectExecutor;
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
/**
* Description of AclWalker
*
* @author Anil
*/
class AclWalker extends SqlWalker
{
/**
* Walks down a FromClause AST node, thereby generating the appropriate SQL.
*
* @return string The SQL.
*/
public function walkFromClause($fromClause)
{
$sql = parent::walkFromClause($fromClause);
$tableAlias = $this->getSQLTableAlias(
$this->getQuery()->getHint('acl.entityRootTableName'),
$this->getQuery()->getHint('acl.entityRootTableDqlAlias')
);
$extraQuery = $this->getQuery()->getHint('acl.extra.query');
$tempAclView = sprintf(' JOIN (%s) ta_ ON %s.id = ta_.id ', $extraQuery, $tableAlias);
return $sql . $tempAclView;
}
}
$aclHelper = $this->container()->get('acl.helper');
$qb = $this->_em->createQueryBuilder();
//create query
$qb = $qb->select('p')
->from('Entity\Product', 'p')
;
$qb->setMaxResults(10);
$qb->setFirstResult(100);
// The query object returned here is a clone obj so, you can always use $qb->getQuery() to get the original query obj
$query = $aclHelper->apply($qb);
$result = $query->getArrayResult();
return $result;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment