CDbAuthManager.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. <?php
  2. /**
  3. * CDbAuthManager class file.
  4. *
  5. * @author Qiang Xue <qiang.xue@gmail.com>
  6. * @link http://www.yiiframework.com/
  7. * @copyright 2008-2013 Yii Software LLC
  8. * @license http://www.yiiframework.com/license/
  9. */
  10. /**
  11. * CDbAuthManager represents an authorization manager that stores authorization information in database.
  12. *
  13. * The database connection is specified by {@link connectionID}. And the database schema
  14. * should be as described in "framework/web/auth/*.sql". You may change the names of
  15. * the three tables used to store the authorization data by setting {@link itemTable},
  16. * {@link itemChildTable} and {@link assignmentTable}.
  17. *
  18. * @property array $authItems The authorization items of the specific type.
  19. *
  20. * @author Qiang Xue <qiang.xue@gmail.com>
  21. * @package system.web.auth
  22. * @since 1.0
  23. */
  24. class CDbAuthManager extends CAuthManager
  25. {
  26. /**
  27. * @var string the ID of the {@link CDbConnection} application component. Defaults to 'db'.
  28. * The database must have the tables as declared in "framework/web/auth/*.sql".
  29. */
  30. public $connectionID='db';
  31. /**
  32. * @var string the name of the table storing authorization items. Defaults to 'AuthItem'.
  33. */
  34. public $itemTable='AuthItem';
  35. /**
  36. * @var string the name of the table storing authorization item hierarchy. Defaults to 'AuthItemChild'.
  37. */
  38. public $itemChildTable='AuthItemChild';
  39. /**
  40. * @var string the name of the table storing authorization item assignments. Defaults to 'AuthAssignment'.
  41. */
  42. public $assignmentTable='AuthAssignment';
  43. /**
  44. * @var CDbConnection the database connection. By default, this is initialized
  45. * automatically as the application component whose ID is indicated as {@link connectionID}.
  46. */
  47. public $db;
  48. private $_usingSqlite;
  49. /**
  50. * Initializes the application component.
  51. * This method overrides the parent implementation by establishing the database connection.
  52. */
  53. public function init()
  54. {
  55. parent::init();
  56. $this->_usingSqlite=!strncmp($this->getDbConnection()->getDriverName(),'sqlite',6);
  57. }
  58. /**
  59. * Performs access check for the specified user.
  60. * @param string $itemName the name of the operation that need access check
  61. * @param mixed $userId the user ID. This should can be either an integer and a string representing
  62. * the unique identifier of a user. See {@link IWebUser::getId}.
  63. * @param array $params name-value pairs that would be passed to biz rules associated
  64. * with the tasks and roles assigned to the user.
  65. * Since version 1.1.11 a param with name 'userId' is added to this array, which holds the value of <code>$userId</code>.
  66. * @return boolean whether the operations can be performed by the user.
  67. */
  68. public function checkAccess($itemName,$userId,$params=array())
  69. {
  70. $assignments=$this->getAuthAssignments($userId);
  71. return $this->checkAccessRecursive($itemName,$userId,$params,$assignments);
  72. }
  73. /**
  74. * Performs access check for the specified user.
  75. * This method is internally called by {@link checkAccess}.
  76. * @param string $itemName the name of the operation that need access check
  77. * @param mixed $userId the user ID. This should can be either an integer and a string representing
  78. * the unique identifier of a user. See {@link IWebUser::getId}.
  79. * @param array $params name-value pairs that would be passed to biz rules associated
  80. * with the tasks and roles assigned to the user.
  81. * Since version 1.1.11 a param with name 'userId' is added to this array, which holds the value of <code>$userId</code>.
  82. * @param array $assignments the assignments to the specified user
  83. * @return boolean whether the operations can be performed by the user.
  84. * @since 1.1.3
  85. */
  86. protected function checkAccessRecursive($itemName,$userId,$params,$assignments)
  87. {
  88. if(($item=$this->getAuthItem($itemName))===null)
  89. return false;
  90. Yii::trace('Checking permission "'.$item->getName().'"','system.web.auth.CDbAuthManager');
  91. if(!isset($params['userId']))
  92. $params['userId'] = $userId;
  93. if($this->executeBizRule($item->getBizRule(),$params,$item->getData()))
  94. {
  95. if(in_array($itemName,$this->defaultRoles))
  96. return true;
  97. if(isset($assignments[$itemName]))
  98. {
  99. $assignment=$assignments[$itemName];
  100. if($this->executeBizRule($assignment->getBizRule(),$params,$assignment->getData()))
  101. return true;
  102. }
  103. $parents=$this->db->createCommand()
  104. ->select('parent')
  105. ->from($this->itemChildTable)
  106. ->where('child=:name', array(':name'=>$itemName))
  107. ->queryColumn();
  108. foreach($parents as $parent)
  109. {
  110. if($this->checkAccessRecursive($parent,$userId,$params,$assignments))
  111. return true;
  112. }
  113. }
  114. return false;
  115. }
  116. /**
  117. * Adds an item as a child of another item.
  118. * @param string $itemName the parent item name
  119. * @param string $childName the child item name
  120. * @return boolean whether the item is added successfully
  121. * @throws CException if either parent or child doesn't exist or if a loop has been detected.
  122. */
  123. public function addItemChild($itemName,$childName)
  124. {
  125. if($itemName===$childName)
  126. throw new CException(Yii::t('yii','Cannot add "{name}" as a child of itself.',
  127. array('{name}'=>$itemName)));
  128. $rows=$this->db->createCommand()
  129. ->select()
  130. ->from($this->itemTable)
  131. ->where('name=:name1 OR name=:name2', array(
  132. ':name1'=>$itemName,
  133. ':name2'=>$childName
  134. ))
  135. ->queryAll();
  136. if(count($rows)==2)
  137. {
  138. if($rows[0]['name']===$itemName)
  139. {
  140. $parentType=$rows[0]['type'];
  141. $childType=$rows[1]['type'];
  142. }
  143. else
  144. {
  145. $childType=$rows[0]['type'];
  146. $parentType=$rows[1]['type'];
  147. }
  148. $this->checkItemChildType($parentType,$childType);
  149. if($this->detectLoop($itemName,$childName))
  150. throw new CException(Yii::t('yii','Cannot add "{child}" as a child of "{name}". A loop has been detected.',
  151. array('{child}'=>$childName,'{name}'=>$itemName)));
  152. $this->db->createCommand()
  153. ->insert($this->itemChildTable, array(
  154. 'parent'=>$itemName,
  155. 'child'=>$childName,
  156. ));
  157. return true;
  158. }
  159. else
  160. throw new CException(Yii::t('yii','Either "{parent}" or "{child}" does not exist.',array('{child}'=>$childName,'{parent}'=>$itemName)));
  161. }
  162. /**
  163. * Removes a child from its parent.
  164. * Note, the child item is not deleted. Only the parent-child relationship is removed.
  165. * @param string $itemName the parent item name
  166. * @param string $childName the child item name
  167. * @return boolean whether the removal is successful
  168. */
  169. public function removeItemChild($itemName,$childName)
  170. {
  171. return $this->db->createCommand()
  172. ->delete($this->itemChildTable, 'parent=:parent AND child=:child', array(
  173. ':parent'=>$itemName,
  174. ':child'=>$childName
  175. )) > 0;
  176. }
  177. /**
  178. * Returns a value indicating whether a child exists within a parent.
  179. * @param string $itemName the parent item name
  180. * @param string $childName the child item name
  181. * @return boolean whether the child exists
  182. */
  183. public function hasItemChild($itemName,$childName)
  184. {
  185. return $this->db->createCommand()
  186. ->select('parent')
  187. ->from($this->itemChildTable)
  188. ->where('parent=:parent AND child=:child', array(
  189. ':parent'=>$itemName,
  190. ':child'=>$childName))
  191. ->queryScalar() !== false;
  192. }
  193. /**
  194. * Returns the children of the specified item.
  195. * @param mixed $names the parent item name. This can be either a string or an array.
  196. * The latter represents a list of item names.
  197. * @return array all child items of the parent
  198. */
  199. public function getItemChildren($names)
  200. {
  201. if(is_string($names))
  202. $condition='parent='.$this->db->quoteValue($names);
  203. elseif(is_array($names) && $names!==array())
  204. {
  205. foreach($names as &$name)
  206. $name=$this->db->quoteValue($name);
  207. $condition='parent IN ('.implode(', ',$names).')';
  208. }
  209. $rows=$this->db->createCommand()
  210. ->select('name, type, description, bizrule, data')
  211. ->from(array(
  212. $this->itemTable,
  213. $this->itemChildTable
  214. ))
  215. ->where($condition.' AND name=child')
  216. ->queryAll();
  217. $children=array();
  218. foreach($rows as $row)
  219. {
  220. if(($data=@unserialize($row['data']))===false)
  221. $data=null;
  222. $children[$row['name']]=new CAuthItem($this,$row['name'],$row['type'],$row['description'],$row['bizrule'],$data);
  223. }
  224. return $children;
  225. }
  226. /**
  227. * Assigns an authorization item to a user.
  228. * @param string $itemName the item name
  229. * @param mixed $userId the user ID (see {@link IWebUser::getId})
  230. * @param string $bizRule the business rule to be executed when {@link checkAccess} is called
  231. * for this particular authorization item.
  232. * @param mixed $data additional data associated with this assignment
  233. * @return CAuthAssignment the authorization assignment information.
  234. * @throws CException if the item does not exist or if the item has already been assigned to the user
  235. */
  236. public function assign($itemName,$userId,$bizRule=null,$data=null)
  237. {
  238. if($this->usingSqlite() && $this->getAuthItem($itemName)===null)
  239. throw new CException(Yii::t('yii','The item "{name}" does not exist.',array('{name}'=>$itemName)));
  240. $this->db->createCommand()
  241. ->insert($this->assignmentTable, array(
  242. 'itemname'=>$itemName,
  243. 'userid'=>$userId,
  244. 'bizrule'=>$bizRule,
  245. 'data'=>serialize($data)
  246. ));
  247. return new CAuthAssignment($this,$itemName,$userId,$bizRule,$data);
  248. }
  249. /**
  250. * Revokes an authorization assignment from a user.
  251. * @param string $itemName the item name
  252. * @param mixed $userId the user ID (see {@link IWebUser::getId})
  253. * @return boolean whether removal is successful
  254. */
  255. public function revoke($itemName,$userId)
  256. {
  257. return $this->db->createCommand()
  258. ->delete($this->assignmentTable, 'itemname=:itemname AND userid=:userid', array(
  259. ':itemname'=>$itemName,
  260. ':userid'=>$userId
  261. )) > 0;
  262. }
  263. /**
  264. * Returns a value indicating whether the item has been assigned to the user.
  265. * @param string $itemName the item name
  266. * @param mixed $userId the user ID (see {@link IWebUser::getId})
  267. * @return boolean whether the item has been assigned to the user.
  268. */
  269. public function isAssigned($itemName,$userId)
  270. {
  271. return $this->db->createCommand()
  272. ->select('itemname')
  273. ->from($this->assignmentTable)
  274. ->where('itemname=:itemname AND userid=:userid', array(
  275. ':itemname'=>$itemName,
  276. ':userid'=>$userId))
  277. ->queryScalar() !== false;
  278. }
  279. /**
  280. * Returns the item assignment information.
  281. * @param string $itemName the item name
  282. * @param mixed $userId the user ID (see {@link IWebUser::getId})
  283. * @return CAuthAssignment the item assignment information. Null is returned if
  284. * the item is not assigned to the user.
  285. */
  286. public function getAuthAssignment($itemName,$userId)
  287. {
  288. $row=$this->db->createCommand()
  289. ->select()
  290. ->from($this->assignmentTable)
  291. ->where('itemname=:itemname AND userid=:userid', array(
  292. ':itemname'=>$itemName,
  293. ':userid'=>$userId))
  294. ->queryRow();
  295. if($row!==false)
  296. {
  297. if(($data=@unserialize($row['data']))===false)
  298. $data=null;
  299. return new CAuthAssignment($this,$row['itemname'],$row['userid'],$row['bizrule'],$data);
  300. }
  301. else
  302. return null;
  303. }
  304. /**
  305. * Returns the item assignments for the specified user.
  306. * @param mixed $userId the user ID (see {@link IWebUser::getId})
  307. * @return array the item assignment information for the user. An empty array will be
  308. * returned if there is no item assigned to the user.
  309. */
  310. public function getAuthAssignments($userId)
  311. {
  312. $rows=$this->db->createCommand()
  313. ->select()
  314. ->from($this->assignmentTable)
  315. ->where('userid=:userid', array(':userid'=>$userId))
  316. ->queryAll();
  317. $assignments=array();
  318. foreach($rows as $row)
  319. {
  320. if(($data=@unserialize($row['data']))===false)
  321. $data=null;
  322. $assignments[$row['itemname']]=new CAuthAssignment($this,$row['itemname'],$row['userid'],$row['bizrule'],$data);
  323. }
  324. return $assignments;
  325. }
  326. /**
  327. * Saves the changes to an authorization assignment.
  328. * @param CAuthAssignment $assignment the assignment that has been changed.
  329. */
  330. public function saveAuthAssignment($assignment)
  331. {
  332. $this->db->createCommand()
  333. ->update($this->assignmentTable, array(
  334. 'bizrule'=>$assignment->getBizRule(),
  335. 'data'=>serialize($assignment->getData()),
  336. ), 'itemname=:itemname AND userid=:userid', array(
  337. 'itemname'=>$assignment->getItemName(),
  338. 'userid'=>$assignment->getUserId()
  339. ));
  340. }
  341. /**
  342. * Returns the authorization items of the specific type and user.
  343. * @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null,
  344. * meaning returning all items regardless of their type.
  345. * @param mixed $userId the user ID. Defaults to null, meaning returning all items even if
  346. * they are not assigned to a user.
  347. * @return array the authorization items of the specific type.
  348. */
  349. public function getAuthItems($type=null,$userId=null)
  350. {
  351. if($type===null && $userId===null)
  352. {
  353. $command=$this->db->createCommand()
  354. ->select()
  355. ->from($this->itemTable);
  356. }
  357. elseif($userId===null)
  358. {
  359. $command=$this->db->createCommand()
  360. ->select()
  361. ->from($this->itemTable)
  362. ->where('type=:type', array(':type'=>$type));
  363. }
  364. elseif($type===null)
  365. {
  366. $command=$this->db->createCommand()
  367. ->select('name,type,description,t1.bizrule,t1.data')
  368. ->from(array(
  369. $this->itemTable.' t1',
  370. $this->assignmentTable.' t2'
  371. ))
  372. ->where('name=itemname AND userid=:userid', array(':userid'=>$userId));
  373. }
  374. else
  375. {
  376. $command=$this->db->createCommand()
  377. ->select('name,type,description,t1.bizrule,t1.data')
  378. ->from(array(
  379. $this->itemTable.' t1',
  380. $this->assignmentTable.' t2'
  381. ))
  382. ->where('name=itemname AND type=:type AND userid=:userid', array(
  383. ':type'=>$type,
  384. ':userid'=>$userId
  385. ));
  386. }
  387. $items=array();
  388. foreach($command->queryAll() as $row)
  389. {
  390. if(($data=@unserialize($row['data']))===false)
  391. $data=null;
  392. $items[$row['name']]=new CAuthItem($this,$row['name'],$row['type'],$row['description'],$row['bizrule'],$data);
  393. }
  394. return $items;
  395. }
  396. /**
  397. * Creates an authorization item.
  398. * An authorization item represents an action permission (e.g. creating a post).
  399. * It has three types: operation, task and role.
  400. * Authorization items form a hierarchy. Higher level items inherit permissions representing
  401. * by lower level items.
  402. * @param string $name the item name. This must be a unique identifier.
  403. * @param integer $type the item type (0: operation, 1: task, 2: role).
  404. * @param string $description description of the item
  405. * @param string $bizRule business rule associated with the item. This is a piece of
  406. * PHP code that will be executed when {@link checkAccess} is called for the item.
  407. * @param mixed $data additional data associated with the item.
  408. * @return CAuthItem the authorization item
  409. * @throws CException if an item with the same name already exists
  410. */
  411. public function createAuthItem($name,$type,$description='',$bizRule=null,$data=null)
  412. {
  413. $this->db->createCommand()
  414. ->insert($this->itemTable, array(
  415. 'name'=>$name,
  416. 'type'=>$type,
  417. 'description'=>$description,
  418. 'bizrule'=>$bizRule,
  419. 'data'=>serialize($data)
  420. ));
  421. return new CAuthItem($this,$name,$type,$description,$bizRule,$data);
  422. }
  423. /**
  424. * Removes the specified authorization item.
  425. * @param string $name the name of the item to be removed
  426. * @return boolean whether the item exists in the storage and has been removed
  427. */
  428. public function removeAuthItem($name)
  429. {
  430. if($this->usingSqlite())
  431. {
  432. $this->db->createCommand()
  433. ->delete($this->itemChildTable, 'parent=:name1 OR child=:name2', array(
  434. ':name1'=>$name,
  435. ':name2'=>$name
  436. ));
  437. $this->db->createCommand()
  438. ->delete($this->assignmentTable, 'itemname=:name', array(
  439. ':name'=>$name,
  440. ));
  441. }
  442. return $this->db->createCommand()
  443. ->delete($this->itemTable, 'name=:name', array(
  444. ':name'=>$name
  445. )) > 0;
  446. }
  447. /**
  448. * Returns the authorization item with the specified name.
  449. * @param string $name the name of the item
  450. * @return CAuthItem the authorization item. Null if the item cannot be found.
  451. */
  452. public function getAuthItem($name)
  453. {
  454. $row=$this->db->createCommand()
  455. ->select()
  456. ->from($this->itemTable)
  457. ->where('name=:name', array(':name'=>$name))
  458. ->queryRow();
  459. if($row!==false)
  460. {
  461. if(($data=@unserialize($row['data']))===false)
  462. $data=null;
  463. return new CAuthItem($this,$row['name'],$row['type'],$row['description'],$row['bizrule'],$data);
  464. }
  465. else
  466. return null;
  467. }
  468. /**
  469. * Saves an authorization item to persistent storage.
  470. * @param CAuthItem $item the item to be saved.
  471. * @param string $oldName the old item name. If null, it means the item name is not changed.
  472. */
  473. public function saveAuthItem($item,$oldName=null)
  474. {
  475. if($this->usingSqlite() && $oldName!==null && $item->getName()!==$oldName)
  476. {
  477. $this->db->createCommand()
  478. ->update($this->itemChildTable, array(
  479. 'parent'=>$item->getName(),
  480. ), 'parent=:whereName', array(
  481. ':whereName'=>$oldName,
  482. ));
  483. $this->db->createCommand()
  484. ->update($this->itemChildTable, array(
  485. 'child'=>$item->getName(),
  486. ), 'child=:whereName', array(
  487. ':whereName'=>$oldName,
  488. ));
  489. $this->db->createCommand()
  490. ->update($this->assignmentTable, array(
  491. 'itemname'=>$item->getName(),
  492. ), 'itemname=:whereName', array(
  493. ':whereName'=>$oldName,
  494. ));
  495. }
  496. $this->db->createCommand()
  497. ->update($this->itemTable, array(
  498. 'name'=>$item->getName(),
  499. 'type'=>$item->getType(),
  500. 'description'=>$item->getDescription(),
  501. 'bizrule'=>$item->getBizRule(),
  502. 'data'=>serialize($item->getData()),
  503. ), 'name=:whereName', array(
  504. ':whereName'=>$oldName===null?$item->getName():$oldName,
  505. ));
  506. }
  507. /**
  508. * Saves the authorization data to persistent storage.
  509. */
  510. public function save()
  511. {
  512. }
  513. /**
  514. * Removes all authorization data.
  515. */
  516. public function clearAll()
  517. {
  518. $this->clearAuthAssignments();
  519. $this->db->createCommand()->delete($this->itemChildTable);
  520. $this->db->createCommand()->delete($this->itemTable);
  521. }
  522. /**
  523. * Removes all authorization assignments.
  524. */
  525. public function clearAuthAssignments()
  526. {
  527. $this->db->createCommand()->delete($this->assignmentTable);
  528. }
  529. /**
  530. * Checks whether there is a loop in the authorization item hierarchy.
  531. * @param string $itemName parent item name
  532. * @param string $childName the name of the child item that is to be added to the hierarchy
  533. * @return boolean whether a loop exists
  534. */
  535. protected function detectLoop($itemName,$childName)
  536. {
  537. if($childName===$itemName)
  538. return true;
  539. foreach($this->getItemChildren($childName) as $child)
  540. {
  541. if($this->detectLoop($itemName,$child->getName()))
  542. return true;
  543. }
  544. return false;
  545. }
  546. /**
  547. * @return CDbConnection the DB connection instance
  548. * @throws CException if {@link connectionID} does not point to a valid application component.
  549. */
  550. protected function getDbConnection()
  551. {
  552. if($this->db!==null)
  553. return $this->db;
  554. elseif(($this->db=Yii::app()->getComponent($this->connectionID)) instanceof CDbConnection)
  555. return $this->db;
  556. else
  557. throw new CException(Yii::t('yii','CDbAuthManager.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.',
  558. array('{id}'=>$this->connectionID)));
  559. }
  560. /**
  561. * @return boolean whether the database is a SQLite database
  562. */
  563. protected function usingSqlite()
  564. {
  565. return $this->_usingSqlite;
  566. }
  567. }