1: <?php
2: /**
3: * CPhpAuthManager 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: /**
12: * CPhpAuthManager represents an authorization manager that stores authorization information in terms of a PHP script file.
13: *
14: * The authorization data will be saved to and loaded from a file
15: * specified by {@link authFile}, which defaults to 'protected/data/auth.php'.
16: *
17: * CPhpAuthManager is mainly suitable for authorization data that is not too big
18: * (for example, the authorization data for a personal blog system).
19: * Use {@link CDbAuthManager} for more complex authorization data.
20: *
21: * @property array $authItems The authorization items of the specific type.
22: *
23: * @author Qiang Xue <qiang.xue@gmail.com>
24: * @package system.web.auth
25: * @since 1.0
26: */
27: class CPhpAuthManager extends CAuthManager
28: {
29: /**
30: * @var string the path of the PHP script that contains the authorization data.
31: * If not set, it will be using 'protected/data/auth.php' as the data file.
32: * Make sure this file is writable by the Web server process if the authorization
33: * needs to be changed.
34: * @see loadFromFile
35: * @see saveToFile
36: */
37: public $authFile;
38:
39: private $_items=array(); // itemName => item
40: private $_children=array(); // itemName, childName => child
41: private $_assignments=array(); // userId, itemName => assignment
42:
43: /**
44: * Initializes the application component.
45: * This method overrides parent implementation by loading the authorization data
46: * from PHP script.
47: */
48: public function init()
49: {
50: parent::init();
51: if($this->authFile===null)
52: $this->authFile=Yii::getPathOfAlias('application.data.auth').'.php';
53: $this->load();
54: }
55:
56: /**
57: * Performs access check for the specified user.
58: * @param string $itemName the name of the operation that need access check
59: * @param mixed $userId the user ID. This can be either an integer or a string representing
60: * the unique identifier of a user. See {@link IWebUser::getId}.
61: * @param array $params name-value pairs that would be passed to biz rules associated
62: * with the tasks and roles assigned to the user.
63: * Since version 1.1.11 a param with name 'userId' is added to this array, which holds the value of <code>$userId</code>.
64: * @return boolean whether the operations can be performed by the user.
65: */
66: public function checkAccess($itemName,$userId,$params=array())
67: {
68: if(!isset($this->_items[$itemName]))
69: return false;
70: $item=$this->_items[$itemName];
71: Yii::trace('Checking permission "'.$item->getName().'"','system.web.auth.CPhpAuthManager');
72: if(!isset($params['userId']))
73: $params['userId'] = $userId;
74: if($this->executeBizRule($item->getBizRule(),$params,$item->getData()))
75: {
76: if(in_array($itemName,$this->defaultRoles))
77: return true;
78: if(isset($this->_assignments[$userId][$itemName]))
79: {
80: $assignment=$this->_assignments[$userId][$itemName];
81: if($this->executeBizRule($assignment->getBizRule(),$params,$assignment->getData()))
82: return true;
83: }
84: foreach($this->_children as $parentName=>$children)
85: {
86: if(isset($children[$itemName]) && $this->checkAccess($parentName,$userId,$params))
87: return true;
88: }
89: }
90: return false;
91: }
92:
93: /**
94: * Adds an item as a child of another item.
95: * @param string $itemName the parent item name
96: * @param string $childName the child item name
97: * @return boolean whether the item is added successfully
98: * @throws CException if either parent or child doesn't exist or if a loop has been detected.
99: */
100: public function addItemChild($itemName,$childName)
101: {
102: if(!isset($this->_items[$childName],$this->_items[$itemName]))
103: throw new CException(Yii::t('yii','Either "{parent}" or "{child}" does not exist.',array('{child}'=>$childName,'{parent}'=>$itemName)));
104: $child=$this->_items[$childName];
105: $item=$this->_items[$itemName];
106: $this->checkItemChildType($item->getType(),$child->getType());
107: if($this->detectLoop($itemName,$childName))
108: throw new CException(Yii::t('yii','Cannot add "{child}" as a child of "{parent}". A loop has been detected.',
109: array('{child}'=>$childName,'{parent}'=>$itemName)));
110: if(isset($this->_children[$itemName][$childName]))
111: throw new CException(Yii::t('yii','The item "{parent}" already has a child "{child}".',
112: array('{child}'=>$childName,'{parent}'=>$itemName)));
113: $this->_children[$itemName][$childName]=$this->_items[$childName];
114: return true;
115: }
116:
117: /**
118: * Removes a child from its parent.
119: * Note, the child item is not deleted. Only the parent-child relationship is removed.
120: * @param string $itemName the parent item name
121: * @param string $childName the child item name
122: * @return boolean whether the removal is successful
123: */
124: public function removeItemChild($itemName,$childName)
125: {
126: if(isset($this->_children[$itemName][$childName]))
127: {
128: unset($this->_children[$itemName][$childName]);
129: return true;
130: }
131: else
132: return false;
133: }
134:
135: /**
136: * Returns a value indicating whether a child exists within a parent.
137: * @param string $itemName the parent item name
138: * @param string $childName the child item name
139: * @return boolean whether the child exists
140: */
141: public function hasItemChild($itemName,$childName)
142: {
143: return isset($this->_children[$itemName][$childName]);
144: }
145:
146: /**
147: * Returns the children of the specified item.
148: * @param mixed $names the parent item name. This can be either a string or an array.
149: * The latter represents a list of item names.
150: * @return array all child items of the parent
151: */
152: public function getItemChildren($names)
153: {
154: if(is_string($names))
155: return isset($this->_children[$names]) ? $this->_children[$names] : array();
156:
157: $children=array();
158: foreach($names as $name)
159: {
160: if(isset($this->_children[$name]))
161: $children=array_merge($children,$this->_children[$name]);
162: }
163: return $children;
164: }
165:
166: /**
167: * Assigns an authorization item to a user.
168: * @param string $itemName the item name
169: * @param mixed $userId the user ID (see {@link IWebUser::getId})
170: * @param string $bizRule the business rule to be executed when {@link checkAccess} is called
171: * for this particular authorization item.
172: * @param mixed $data additional data associated with this assignment
173: * @return CAuthAssignment the authorization assignment information.
174: * @throws CException if the item does not exist or if the item has already been assigned to the user
175: */
176: public function assign($itemName,$userId,$bizRule=null,$data=null)
177: {
178: if(!isset($this->_items[$itemName]))
179: throw new CException(Yii::t('yii','Unknown authorization item "{name}".',array('{name}'=>$itemName)));
180: elseif(isset($this->_assignments[$userId][$itemName]))
181: throw new CException(Yii::t('yii','Authorization item "{item}" has already been assigned to user "{user}".',
182: array('{item}'=>$itemName,'{user}'=>$userId)));
183: else
184: return $this->_assignments[$userId][$itemName]=new CAuthAssignment($this,$itemName,$userId,$bizRule,$data);
185: }
186:
187: /**
188: * Revokes an authorization assignment from a user.
189: * @param string $itemName the item name
190: * @param mixed $userId the user ID (see {@link IWebUser::getId})
191: * @return boolean whether removal is successful
192: */
193: public function revoke($itemName,$userId)
194: {
195: if(isset($this->_assignments[$userId][$itemName]))
196: {
197: unset($this->_assignments[$userId][$itemName]);
198: return true;
199: }
200: else
201: return false;
202: }
203:
204: /**
205: * Returns a value indicating whether the item has been assigned to the user.
206: * @param string $itemName the item name
207: * @param mixed $userId the user ID (see {@link IWebUser::getId})
208: * @return boolean whether the item has been assigned to the user.
209: */
210: public function isAssigned($itemName,$userId)
211: {
212: return isset($this->_assignments[$userId][$itemName]);
213: }
214:
215: /**
216: * Returns the item assignment information.
217: * @param string $itemName the item name
218: * @param mixed $userId the user ID (see {@link IWebUser::getId})
219: * @return CAuthAssignment the item assignment information. Null is returned if
220: * the item is not assigned to the user.
221: */
222: public function getAuthAssignment($itemName,$userId)
223: {
224: return isset($this->_assignments[$userId][$itemName])?$this->_assignments[$userId][$itemName]:null;
225: }
226:
227: /**
228: * Returns the item assignments for the specified user.
229: * @param mixed $userId the user ID (see {@link IWebUser::getId})
230: * @return array the item assignment information for the user. An empty array will be
231: * returned if there is no item assigned to the user.
232: */
233: public function getAuthAssignments($userId)
234: {
235: return isset($this->_assignments[$userId])?$this->_assignments[$userId]:array();
236: }
237:
238: /**
239: * Returns the authorization items of the specific type and user.
240: * @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null,
241: * meaning returning all items regardless of their type.
242: * @param mixed $userId the user ID. Defaults to null, meaning returning all items even if
243: * they are not assigned to a user.
244: * @return array the authorization items of the specific type.
245: */
246: public function getAuthItems($type=null,$userId=null)
247: {
248: if($type===null && $userId===null)
249: return $this->_items;
250: $items=array();
251: if($userId===null)
252: {
253: foreach($this->_items as $name=>$item)
254: {
255: if($item->getType()==$type)
256: $items[$name]=$item;
257: }
258: }
259: elseif(isset($this->_assignments[$userId]))
260: {
261: foreach($this->_assignments[$userId] as $assignment)
262: {
263: $name=$assignment->getItemName();
264: if(isset($this->_items[$name]) && ($type===null || $this->_items[$name]->getType()==$type))
265: $items[$name]=$this->_items[$name];
266: }
267: }
268: return $items;
269: }
270:
271: /**
272: * Creates an authorization item.
273: * An authorization item represents an action permission (e.g. creating a post).
274: * It has three types: operation, task and role.
275: * Authorization items form a hierarchy. Higher level items inherit permissions representing
276: * by lower level items.
277: * @param string $name the item name. This must be a unique identifier.
278: * @param integer $type the item type (0: operation, 1: task, 2: role).
279: * @param string $description description of the item
280: * @param string $bizRule business rule associated with the item. This is a piece of
281: * PHP code that will be executed when {@link checkAccess} is called for the item.
282: * @param mixed $data additional data associated with the item.
283: * @return CAuthItem the authorization item
284: * @throws CException if an item with the same name already exists
285: */
286: public function createAuthItem($name,$type,$description='',$bizRule=null,$data=null)
287: {
288: if(isset($this->_items[$name]))
289: throw new CException(Yii::t('yii','Unable to add an item whose name is the same as an existing item.'));
290: return $this->_items[$name]=new CAuthItem($this,$name,$type,$description,$bizRule,$data);
291: }
292:
293: /**
294: * Removes the specified authorization item.
295: * @param string $name the name of the item to be removed
296: * @return boolean whether the item exists in the storage and has been removed
297: */
298: public function removeAuthItem($name)
299: {
300: if(isset($this->_items[$name]))
301: {
302: foreach($this->_children as &$children)
303: unset($children[$name]);
304: foreach($this->_assignments as &$assignments)
305: unset($assignments[$name]);
306: unset($this->_items[$name]);
307: return true;
308: }
309: else
310: return false;
311: }
312:
313: /**
314: * Returns the authorization item with the specified name.
315: * @param string $name the name of the item
316: * @return CAuthItem the authorization item. Null if the item cannot be found.
317: */
318: public function getAuthItem($name)
319: {
320: return isset($this->_items[$name])?$this->_items[$name]:null;
321: }
322:
323: /**
324: * Saves an authorization item to persistent storage.
325: * @param CAuthItem $item the item to be saved.
326: * @param string $oldName the old item name. If null, it means the item name is not changed.
327: */
328: public function saveAuthItem($item,$oldName=null)
329: {
330: if($oldName!==null && ($newName=$item->getName())!==$oldName) // name changed
331: {
332: if(isset($this->_items[$newName]))
333: throw new CException(Yii::t('yii','Unable to change the item name. The name "{name}" is already used by another item.',array('{name}'=>$newName)));
334: if(isset($this->_items[$oldName]) && $this->_items[$oldName]===$item)
335: {
336: unset($this->_items[$oldName]);
337: $this->_items[$newName]=$item;
338: if(isset($this->_children[$oldName]))
339: {
340: $this->_children[$newName]=$this->_children[$oldName];
341: unset($this->_children[$oldName]);
342: }
343: foreach($this->_children as &$children)
344: {
345: if(isset($children[$oldName]))
346: {
347: $children[$newName]=$children[$oldName];
348: unset($children[$oldName]);
349: }
350: }
351: foreach($this->_assignments as &$assignments)
352: {
353: if(isset($assignments[$oldName]))
354: {
355: $assignments[$newName]=$assignments[$oldName];
356: unset($assignments[$oldName]);
357: }
358: }
359: }
360: }
361: }
362:
363: /**
364: * Saves the changes to an authorization assignment.
365: * @param CAuthAssignment $assignment the assignment that has been changed.
366: */
367: public function saveAuthAssignment($assignment)
368: {
369: }
370:
371: /**
372: * Saves authorization data into persistent storage.
373: * If any change is made to the authorization data, please make
374: * sure you call this method to save the changed data into persistent storage.
375: */
376: public function save()
377: {
378: $items=array();
379: foreach($this->_items as $name=>$item)
380: {
381: $items[$name]=array(
382: 'type'=>$item->getType(),
383: 'description'=>$item->getDescription(),
384: 'bizRule'=>$item->getBizRule(),
385: 'data'=>$item->getData(),
386: );
387: if(isset($this->_children[$name]))
388: {
389: foreach($this->_children[$name] as $child)
390: $items[$name]['children'][]=$child->getName();
391: }
392: }
393:
394: foreach($this->_assignments as $userId=>$assignments)
395: {
396: foreach($assignments as $name=>$assignment)
397: {
398: if(isset($items[$name]))
399: {
400: $items[$name]['assignments'][$userId]=array(
401: 'bizRule'=>$assignment->getBizRule(),
402: 'data'=>$assignment->getData(),
403: );
404: }
405: }
406: }
407:
408: $this->saveToFile($items,$this->authFile);
409: }
410:
411: /**
412: * Loads authorization data.
413: */
414: public function load()
415: {
416: $this->clearAll();
417:
418: $items=$this->loadFromFile($this->authFile);
419:
420: foreach($items as $name=>$item)
421: $this->_items[$name]=new CAuthItem($this,$name,$item['type'],$item['description'],$item['bizRule'],$item['data']);
422:
423: foreach($items as $name=>$item)
424: {
425: if(isset($item['children']))
426: {
427: foreach($item['children'] as $childName)
428: {
429: if(isset($this->_items[$childName]))
430: $this->_children[$name][$childName]=$this->_items[$childName];
431: }
432: }
433: if(isset($item['assignments']))
434: {
435: foreach($item['assignments'] as $userId=>$assignment)
436: {
437: $this->_assignments[$userId][$name]=new CAuthAssignment($this,$name,$userId,$assignment['bizRule'],$assignment['data']);
438: }
439: }
440: }
441: }
442:
443: /**
444: * Removes all authorization data.
445: */
446: public function clearAll()
447: {
448: $this->clearAuthAssignments();
449: $this->_children=array();
450: $this->_items=array();
451: }
452:
453: /**
454: * Removes all authorization assignments.
455: */
456: public function clearAuthAssignments()
457: {
458: $this->_assignments=array();
459: }
460:
461: /**
462: * Checks whether there is a loop in the authorization item hierarchy.
463: * @param string $itemName parent item name
464: * @param string $childName the name of the child item that is to be added to the hierarchy
465: * @return boolean whether a loop exists
466: */
467: protected function detectLoop($itemName,$childName)
468: {
469: if($childName===$itemName)
470: return true;
471: if(!isset($this->_children[$childName], $this->_items[$itemName]))
472: return false;
473:
474: foreach($this->_children[$childName] as $child)
475: {
476: if($this->detectLoop($itemName,$child->getName()))
477: return true;
478: }
479: return false;
480: }
481:
482: /**
483: * Loads the authorization data from a PHP script file.
484: * @param string $file the file path.
485: * @return array the authorization data
486: * @see saveToFile
487: */
488: protected function loadFromFile($file)
489: {
490: if(is_file($file))
491: return require($file);
492: else
493: return array();
494: }
495:
496: /**
497: * Saves the authorization data to a PHP script file.
498: * @param array $data the authorization data
499: * @param string $file the file path.
500: * @see loadFromFile
501: */
502: protected function saveToFile($data,$file)
503: {
504: file_put_contents($file,"<?php\nreturn ".var_export($data,true).";\n");
505: }
506: }
507: