2011-07-08 9 views
30

वेब अनुप्रयोग में सिम्फनी 2 में एसीएल कार्यान्वयन का उपयोग करते समय, हम एक उपयोग मामले में आ गए हैं जहां एसीएल का उपयोग करने का सुझाया गया तरीका (एक डोमेन ऑब्जेक्ट पर उपयोगकर्ता अनुमतियों की जांच करना) असुरक्षित हो जाता है। इस प्रकार, हम सोचते हैं कि एसीएल एपीआई का कुछ हिस्सा मौजूद है जिसका उपयोग हम अपनी समस्या को हल करने के लिए कर सकते हैं।किसी निश्चित उपयोगकर्ता की अनुमतियों (उदा। EDIT) के अनुसार डोमेन-ऑब्जेक्ट्स की सूची फ़िल्टर करने के लिए एसीएल का उपयोग कैसे करें?

उपयोग केस एक नियंत्रक में है जो टेम्पलेट में प्रस्तुत किए जाने वाले डोमेन ऑब्जेक्ट्स की एक सूची तैयार करता है, ताकि उपयोगकर्ता यह चुन सकें कि वह कौन सी ऑब्जेक्ट्स संपादित करना चाहती है। उपयोगकर्ता को डेटाबेस में सभी ऑब्जेक्ट्स को संपादित करने की अनुमति नहीं है, इसलिए सूची तदनुसार फ़िल्टर की जानी चाहिए।

यह कर सकता है (अन्य समाधान के अलावा) दो रणनीतियों के अनुसार किया जाना:

1) एक क्वेरी फिल्टर है कि वस्तु के लिए वर्तमान उपयोगकर्ता के एसीएल से मान्य वस्तु आईडी (या वस्तुओं) के साथ किसी क्वेरी जोड़ देता है। अर्थात:

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 में इन रणनीतियों में से किसी एक (या वांछित परिणाम प्राप्त करने में कुछ) का कोई कार्यान्वयन है?

उत्तर

18

मान लिया जाये कि आप डोमेन वस्तुओं है कि आप जाँच करना चाहते हैं का एक संग्रह है तो आप उसे isGranted() कॉल करने के लिये पहले बैच लोड करने के लिए security.acl.provider सेवा के findAcls() विधि का उपयोग कर सकते हैं।

शर्तें:

डाटाबेस मेरी डेटाबेस से एक यादृच्छिक उपयोगकर्ता के लिए MaskBuilder::MASK_OWNER की वस्तु अनुमतियों और भूमिका IS_AUTHENTICATED_ANONYMOUSLY के लिए MASK_VIEW के वर्ग अनुमतियों के साथ, परीक्षण संस्थाओं के साथ बसा हुआ था; MASK_CREATEROLE_USER के लिए; और MASK_EDIT और MASK_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 मिनट में किया गया था; जब मेरे पास मौका होता है, तो मैं एसीएल सिस्टम के किसी अन्य सहायक उपयोग के बारे में और यहां रिपोर्ट करने के लिए प्रासंगिक तरीकों से अधिक अच्छी तरह से समीक्षा करता हूं।

+8

लेकिन मुझे नहीं लगता कि आप इसके साथ बड़े डेटासेट को कैसे संभालना चाहते हैं। कल्पना कीजिए कि आपके पास 10000 रिकॉर्ड हैं, यहां तक ​​कि एक पेजिनेशन के साथ, मुझे यह जानना होगा कि उपयोगकर्ता द्वारा कितने रिकॉर्ड स्वामित्व में हैं। क्या टेबल में सभी इकाइयों के माध्यम से घुसपैठ किए बिना कोई रास्ता है? – stoefln

+2

मैं पूरी तरह से @stoefln से सहमत हूं, ऐसे समाधान बड़े डेटासेट के साथ अव्यवहारिक है। [डिएगो का जवाब] (http://stackoverflow.com/a/7452467/539560) बेहतर दिखता है लेकिन मुझे इसके बारे में निश्चित नहीं है ... – Xuni

+0

+1 थोड़ी देर के लिए दस्तावेज़ों पर निराशाजनक रूप से घूरने के बाद मैंने अंततः थोड़ा सा एक (थोड़ा असंबंधित) लेकिन इस उत्तर को पढ़ने में बहुत उपयोगी एसीएल epiphany। उसके लिए धन्यवाद :) –

-3

जॉइन का उपयोग करें, और यदि आप सिद्धांत का उपयोग कर रहे हैं, तो इसे आपके लिए जुड़ने के लिए प्राप्त करें, क्योंकि वे लगभग हमेशा तेज होते हैं। इसलिए आपको अपनी एसीएल स्कीमा डिज़ाइन करनी चाहिए जो इन तेज़ फ़िल्टर कर रहे हैं।

+2

हम सिम्फनी 2 में एसीएल कार्यान्वयन का उपयोग कर रहे हैं, इसलिए हमारे पास एसीएल स्कीमा और इसकी जटिलता पर कोई प्रभाव नहीं है। Symonfy'2 एसीएल स्कीमा की संरचना को देखते हुए इस तरह के शामिल होने के साथ हर सवाल को जोड़ना असुरक्षित होगा (मुख्य रूप से पहचान संकेत, एसीई पदानुक्रम और एसीई प्रेसीडेंस के कारण)। मैं इस सवाल को और अधिक स्पष्ट रूप से बताऊंगा कि हम सिम्फनी 2 के एसीएल कार्यान्वयन का उपयोग कर रहे हैं। –

9

इकाइयों पर भर्ती करना संभव नहीं है यदि आपके पास कुछ हज़ारवां इकाइयां हैं - यह धीमी गति से और अधिक स्मृति का उपभोग करेगी, जिससे आप सिद्धांत बैचिंग क्षमताओं का उपयोग कर सकें, इस प्रकार आपका कोड अधिक जटिल (और अप्रभावी हो क्योंकि सभी के बाद आपको क्वेरी बनाने के लिए केवल आईडी की आवश्यकता है - पूरे एसीएल/इकाइयों को स्मृति में नहीं)

हमने इस समस्या को हल करने के लिए क्या किया है, 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) 
    ); 

} 

फिर इस विधि को वास्तविक पी से कॉल करना ublic विधि है कि संस्थाओं आईडी हो जाता है:

/** 
* 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 

कमियां: यह तरीकों में रखना सुधार की एसीएल विरासत और रणनीतियों की जटिलताओं को ध्यान में रखते हैं, लेकिन सरल उपयोग के मामलों के लिए यह ठीक काम करता है। दोहराव वाली डबल क्वेरी (कक्षा वर्ग के साथ एक, ओब्जेक्ट-लेवल वाला दूसरा) से बचने के लिए भी एक कैश लागू किया जाना चाहिए

0

सिंपनी सिम्फनी एसीएल आवेदन पर वापस और सॉर्टिंग के रूप में इसका उपयोग करके, अच्छा दृष्टिकोण नहीं है। आप एक साथ आवेदन के 2 या 3 परतों को मिश्रण और जोड़ रहे हैं। एसीएल कार्यक्षमता "हाँ/नहीं" का जवाब देने के लिए है "क्या मुझे ऐसा करने की अनुमति है?" यदि आपको किसी प्रकार के स्वामित्व/संपादन योग्य लेखों की आवश्यकता है, तो आप किसी अन्य तालिका से मानदंडों द्वारा बनाए गए बी या समूह निर्मितबी जैसे कुछ कॉलम का उपयोग कर सकते हैं। कुछ उपयोगकर्ता समूह या खाते।