33

WebアプリケーションでSymfony2のACL実装を使用する場合、ACLを使用するための提案された方法(単一のドメインオブジェクトに対するユーザーのアクセス許可をチェックする)が実行不可能になるユースケースに遭遇しました。したがって、問題を解決するために使用できるACLAPIの一部が存在するかどうか疑問に思います。

ユースケースは、テンプレートに表示されるドメインオブジェクトのリストを準備するコントローラーであり、ユーザーは編集するオブジェクトを選択できます。ユーザーにはデータベース内のすべてのオブジェクトを編集する権限がないため、それに応じてリストをフィルタリングする必要があります。

これは(他のソリューションの中でも)2つの戦略に従って実行できます。

1)オブジェクト(または複数のオブジェクト)の現在のユーザーのACLからの有効なオブジェクトIDを指定されたクエリに追加するクエリフィルター。すなわち:

WHERE <other conditions> AND u.id IN(<list of legal object ids here>)

2)完全なリストがデータベースから取得された後、ユーザーが適切な権限を持たないオブジェクトを削除するクエリ後のフィルター。すなわち:

$objs   = <query for objects>
$objIds = <getting all the permitted obj ids from the ACL>
for ($obj in $objs) {
    if (in_array($obj.id, $objIds) { $result[] = $obj; } 
}
return $result;

最初の戦略は、データベースがすべてのフィルタリング作業を実行し、両方とも2つのデータベースクエリを必要とするため、望ましい方法です。1つはACL用、もう1つは実際のクエリ用ですが、これはおそらく避けられません。

Symfony2にこれらの戦略の1つ(または望ましい結果を達成する何か)の実装はありますか?

4

4 に答える 4

20

チェックするドメインオブジェクトのコレクションがあると仮定すると、security.acl.providerサービスのメソッドを使用して、呼び出しfindAcls()の前にバッチロードを行うことができます。isGranted()

条件:

データベースにはテストエンティティが入力され、データベースからのランダムユーザーのオブジェクト権限とロールMaskBuilder::MASK_OWNERのクラス権限が設定されました。のために ; およびと。_MASK_VIEWIS_AUTHENTICATED_ANONYMOUSLYMASK_CREATEROLE_USERMASK_EDITMASK_DELETEROLE_ADMIN

テストコード:

$repo = $this->getDoctrine()->getRepository('Foo\Bundle\Entity\Bar');
$securityContext = $this->get('security.context');
$aclProvider = $this->get('security.acl.provider');

$barCollection = $repo->findAll();

$oids = array();
foreach ($barCollection as $bar) {
    $oid = ObjectIdentity::fromDomainObject($bar);
    $oids[] = $oid;
}

$aclProvider->findAcls($oids); // preload Acls from database

foreach ($barCollection as $bar) {
    if ($securityContext->isGranted('EDIT', $bar)) {
        // permitted
    } else {
        // denied
    }
}

結果:

呼び出すと$aclProvider->findAcls($oids);、プロファイラーは、私のリクエストに3つのデータベースクエリが含まれていることを示します(匿名ユーザーとして)。

の呼び出しがないfindAcls()場合、同じリクエストに51のクエリが含まれていました。

findAcls()メソッドは30のバッチで読み込まれるため(バッチごとに2つのクエリを使用)、データセットが大きくなるとクエリの数が増えることに注意してください。このテストは、一日の終わりに約15分で行われました。機会があれば、関連する方法を詳しく調べて、ACLシステムの他の有用な使用法があるかどうかを確認し、ここに報告します。

于 2011-09-06T23:36:36.143 に答える
9

数千のエンティティがある場合、エンティティを反復処理することはできません。これにより、速度が低下し、メモリの消費量が増え、ドクトリンバッチ機能を使用せざるを得なくなり、コードがより複雑になります(結局、必要なのはクエリを実行するためのID-メモリ内のacl/エンティティ全体ではありません)

この問題を解決するために行ったのは、acl.providerサービスを独自のサービスに置き換え、そのサービスにデータベースに直接クエリを実行するメソッドを追加することです。

private function _getEntitiesIdsMatchingRoleMaskSql($className, array $roles, $requiredMask)
{
    $rolesSql = array();
    foreach($roles as $role) {
        $rolesSql[] = 's.identifier = ' . $this->connection->quote($role);
    }
    $rolesSql =  '(' . implode(' OR ', $rolesSql) . ')';

    $sql = <<<SELECTCLAUSE
        SELECT 
            oid.object_identifier
        FROM 
            {$this->options['entry_table_name']} e
        JOIN 
            {$this->options['oid_table_name']} oid ON (
            oid.class_id = e.class_id
        )
        JOIN {$this->options['sid_table_name']} s ON (
            s.id = e.security_identity_id
        )     
        JOIN {$this->options['class_table_nambe']} class ON (
            class.id = e.class_id
        )
        WHERE 
            {$this->connection->getDatabasePlatform()->getIsNotNullExpression('e.object_identity_id')} AND
            (e.mask & %d) AND
            $rolesSql AND
            class.class_type = %s
       GROUP BY
            oid.object_identifier    
SELECTCLAUSE;

    return sprintf(
        $sql,
        $requiredMask,
        $this->connection->quote($role),
        $this->connection->quote($className)
    );

} 

次に、エンティティIDを取得する実際のパブリックメソッドからこのメソッドを呼び出します。

/**
 * Get the entities Ids for the className that match the given role & mask
 * 
 * @param string $className
 * @param string $roles
 * @param integer $mask 
 * @param bool $asString - Return a comma-delimited string with the ids instead of an array
 * 
 * @return bool|array|string - True if its allowed to all entities, false if its not
 *          allowed, array or string depending on $asString parameter.
 */
public function getAllowedEntitiesIds($className, array $roles, $mask, $asString = true)
{

    // Check for class-level global permission (its a very similar query to the one
    // posted above
    // If there is a class-level grant permission, then do not query object-level
    if ($this->_maskMatchesRoleForClass($className, $roles, $requiredMask)) {
        return true;
    }         

    // Query the database for ACE's matching the mask for the given roles
    $sql = $this->_getEntitiesIdsMatchingRoleMaskSql($className, $roles, $mask);
    $ids = $this->connection->executeQuery($sql)->fetchAll(\PDO::FETCH_COLUMN);

    // No ACEs found
    if (!count($ids)) {
        return false;
    }

    if ($asString) {
        return implode(',', $ids);
    }

    return $ids;
}

このようにして、コードを使用してDQLクエリにフィルターを追加できます。

// Some action in a controller or form handler...

// This service is our own aclProvider version with the methods mentioned above
$aclProvider = $this->get('security.acl.provider');

$ids = $aclProvider->getAllowedEntitiesIds('SomeEntityClass', array('role1'), MaskBuilder::VIEW, true);

if (is_string($ids)) {
   $queryBuilder->andWhere("entity.id IN ($ids)");
}
// No ACL found: deny all
elseif ($ids===false) {
   $queryBuilder->andWhere("entity.id = 0")
}
elseif ($ids===true) {
   // Global-class permission: allow all
}

// Run query...etc

欠点:このメソッドは、ACLの継承と戦略の複雑さを考慮して改善する必要がありますが、単純なユースケースでは問題なく機能します。また、繰り返しの二重クエリを回避するためにキャッシュを実装する必要があります(1つはクラスレベル、もう1つはobjetcレベル)

于 2011-09-17T04:39:42.667 に答える
0

Symfony ACLをアプリケーションに結合してソートとして使用することは、適切なアプローチではありません。2層または3層のアプリケーションを混合および結合しています。ACL機能は、「はい/いいえ」と答えて「これを行うことはできますか?」という質問に答えることです。ある種の所有/編集可能な記事が必要な場合は、CreatedByのような列を使用したり、別のテーブルの条件によってCreatedByをグループ化したりできます。一部のユーザーグループまたはアカウント。

于 2015-09-11T23:38:02.470 に答える
-3

結合を使用します。Doctrineを使用している場合は、ほとんどの場合高速であるため、結合を生成するように取得します。したがって、これらの高速フィルターを実行できるようにACLスキーマを設計する必要があります。

于 2011-07-08T19:13:11.940 に答える