Gentics Portal.Node PHP API
 All Classes Namespaces Functions Variables Pages
RAuthorizer.php
1 <?php
2 /**
3  * Rights authorizer component class file.
4  *
5  * @author Christoffer Niska <cniska@live.com>
6  * @copyright Copyright &copy; 2010 Christoffer Niska
7  * @since 0.5
8  */
9 class RAuthorizer extends CApplicationComponent {
10 
11  /**
12  * @property string the name of the superuser role.
13  */
14  public $superuserName;
15  /**
16  * @property RDbAuthManager the authorization manager.
17  */
18  private $_authManager;
19 
20  /**
21  * Initializes the authorizer.
22  */
23  public function init()
24  {
25  parent::init();
26 
27  $this->_authManager = Yii::app()->getAuthManager();
28  }
29 
30  /**
31  * Returns the a list of all roles.
32  *
33  * @param boolean $includeSuperuser whether to include the superuser.
34  * @param boolean $sort whether to sort the items by their weights.
35  *
36  * @return the roles.
37  */
38  public function getRoles($includeSuperuser = true, $sort = true)
39  {
40  $exclude = $includeSuperuser===false ? array($this->superuserName) : array();
41  $roles = $this->getAuthItems(CAuthItem::TYPE_ROLE, null, null, $sort, $exclude);
42  $roles = $this->attachAuthItemBehavior($roles);
43  return $roles;
44  }
45 
46  /**
47  * Creates an authorization item.
48  *
49  * @param string $name the item name. This must be a unique identifier.
50  * @param integer $type the item type (0: operation, 1: task, 2: role).
51  * @param string $description the description for the item.
52  * @param string $bizRule business rule associated with the item. This is a piece of
53  * PHP code that will be executed when {@link checkAccess} is called for the item.
54  * @param mixed $data additional data associated with the item.
55  *
56  * @return CAuthItem the authorization item
57  */
58  public function createAuthItem($name, $type, $description = '', $bizRule = null, $data = null)
59  {
60  $bizRule = $bizRule!=='' ? $bizRule : null;
61 
62  if ($data!==null) {
63  $data = $data!=='' ? $this->sanitizeExpression($data.';') : null;
64  }
65 
66  return $this->_authManager->createAuthItem($name, $type, $description, $bizRule, $data);
67  }
68 
69  /**
70  * Updates an authorization item.
71  *
72  * @param string $oldName the item name. This must be a unique identifier.
73  * @param integer $name the item type (0: operation, 1: task, 2: role).
74  * @param string $description the description for the item.
75  * @param string $bizRule business rule associated with the item. This is a piece of
76  * PHP code that will be executed when {@link checkAccess} is called for the item.
77  * @param mixed $data additional data associated with the item.
78  */
79  public function updateAuthItem($oldName, $name, $description = '', $bizRule = null, $data = null)
80  {
81  $authItem = $this->_authManager->getAuthItem($oldName);
82  $authItem->name = $name;
83  $authItem->description = $description!=='' ? $description : null;
84  $authItem->bizRule = $bizRule!=='' ? $bizRule : null;
85 
86  // Make sure that data is not already serialized.
87  if (@unserialize($data)===false) {
88  $authItem->data = $data!=='' ? $this->sanitizeExpression($data.';') : null;
89  }
90 
91  $this->_authManager->saveAuthItem($authItem, $oldName);
92  }
93 
94  /**
95  * Returns the authorization items of the specific type and user.
96  *
97  * @param mixed $types the item type (0: operation, 1: task, 2: role). Defaults to null,
98  * meaning returning all items regardless of their type.
99  * @param mixed $userId the user ID. Defaults to null, meaning returning all items even if
100  * they are not assigned to a user.
101  * @param CAuthItem $parent the item for which to get the select options.
102  * @param boolean $sort sort items by to weights.
103  * @param array $exclude the items to be excluded.
104  *
105  * @return array the authorization items of the specific type.
106  */
107  public function getAuthItems($types = null, $userId = null, CAuthItem $parent = null, $sort = true, $exclude = array())
108  {
109  // We have none or a single type.
110  if ($types!==(array)$types) {
111  $items = $this->_authManager->getAuthItems($types, $userId, $sort);
112  // We have multiple types.
113  } else {
114  $typeItemList = array();
115  foreach ($types as $type) {
116  $typeItemList[$type] = $this->_authManager->getAuthItems($type, $userId, $sort);
117  }
118 
119  // Merge the authorization items preserving the keys.
120  $items = array();
121  foreach ($typeItemList as $typeItems) {
122  $items = $this->mergeAuthItems($items, $typeItems);
123  }
124  }
125 
126  $items = $this->excludeInvalidAuthItems($items, $parent, $exclude);
127  $items = $this->attachAuthItemBehavior($items, $userId, $parent);
128 
129  return $items;
130  }
131 
132  /**
133  * Merges two arrays with authorization items preserving the keys.
134  *
135  * @param array $array1 the items to merge to.
136  * @param array $array2 the items to merge from.
137  *
138  * @return array the merged items.
139  */
140  protected function mergeAuthItems($array1, $array2)
141  {
142  foreach ($array2 as $itemName => $item) {
143  if (isset($array1[$itemName])===false) {
144  $array1[$itemName] = $item;
145  }
146  }
147 
148  return $array1;
149  }
150 
151  /**
152  * Excludes invalid authorization items.
153  * When an item is provided its parents and children are excluded aswell.
154  *
155  * @param array $items the authorization items to process.
156  * @param CAuthItem $parent the item to check valid authorization items for.
157  * @param array $exclude additional items to be excluded.
158  *
159  * @return array valid authorization items.
160  */
161  protected function excludeInvalidAuthItems($items, CAuthItem $parent = null, $exclude = array())
162  {
163  // We are getting authorization items valid for a certain item
164  // exclude its parents and children aswell.
165  if ($parent!==null) {
166  $exclude[] = $parent->name;
167  foreach ($parent->getChildren() as $childName => $child) {
168  $exclude[] = $childName;
169  }
170 
171  // Exclude the parents recursively to avoid inheritance loops.
172  $parentNames = array_keys($this->getAuthItemParents($parent->name));
173  $exclude = array_merge($parentNames, $exclude);
174  }
175 
176  // Unset the items that are supposed to be excluded.
177  foreach ($exclude as $itemName) {
178  if (isset($items[$itemName])) {
179  unset($items[$itemName]);
180  }
181  }
182 
183  return $items;
184  }
185 
186  /**
187  * Returns the parents of the specified authorization item.
188  *
189  * @param mixed $item the item name for which to get its parents.
190  * @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null,
191  * meaning returning all items regardless of their type.
192  * @param string $parentName the name of the item in which permissions to search.
193  * @param boolean $direct whether we want the specified items parent or all parents.
194  *
195  * @return array the names of the parent items.
196  */
197  public function getAuthItemParents($item, $type = null, $parentName = null, $direct = false)
198  {
199  if (($item instanceof CAuthItem)===false) {
200  $item = $this->_authManager->getAuthItem($item);
201  }
202 
203  $permissions = $this->getPermissions($parentName);
204  $parentNames = $this->getAuthItemParentsRecursive($item->name, $permissions, $direct);
205  $parents = $this->_authManager->getAuthItemsByNames($parentNames);
206  $parents = $this->attachAuthItemBehavior($parents, null, $item);
207 
208  if ($type!==null) {
209  foreach ($parents as $parentName => $parent) {
210  if ((int)$parent->type!==$type) {
211  unset($parents[$parentName]);
212  }
213  }
214  }
215 
216  return $parents;
217  }
218 
219  /**
220  * Returns the parents of the specified authorization item recursively.
221  *
222  * @param string $itemName the item name for which to get its parents.
223  * @param array $items the items to process.
224  * @param bool $direct whether we want the specified items parent or all parents.
225  *
226  * @return the names of the parents items recursively.
227  */
228  private function getAuthItemParentsRecursive($itemName, $items, $direct)
229  {
230  $parents = array();
231  foreach ($items as $childName => $children) {
232  if ($children!==array()) {
233  if (isset($children[$itemName])) {
234  if (isset($parents[$childName])===false) {
235  $parents[$childName] = $childName;
236  }
237  } else {
238  if (($p = $this->getAuthItemParentsRecursive($itemName, $children, $direct))!==array()) {
239  if ($direct===false && isset($parents[$childName])===false) {
240  $parents[$childName] = $childName;
241  }
242 
243  $parents = array_merge($parents, $p);
244  }
245  }
246  }
247  }
248 
249  return $parents;
250  }
251 
252  /**
253  * Returns the children for the specified authorization item recursively.
254  *
255  * @param mixed $item the item for which to get its children.
256  * @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null,
257  * meaning returning all items regardless of their type.
258  *
259  * @return array the names of the item's children.
260  */
261  public function getAuthItemChildren($item, $type = null)
262  {
263  if (($item instanceof CAuthItem)===false) {
264  $item = $this->_authManager->getAuthItem($item);
265  }
266 
267  $childrenNames = array();
268  foreach ($item->getChildren() as $childName => $child) {
269  if ($type===null || (int)$child->type===$type) {
270  $childrenNames[] = $childName;
271  }
272  }
273 
274  $children = $this->_authManager->getAuthItemsByNames($childrenNames);
275  $children = $this->attachAuthItemBehavior($children, null, $item);
276 
277  return $children;
278  }
279 
280  /**
281  * Attaches the rights authorization item behavior to the given item.
282  *
283  * @param mixed $items the item or items to which attach the behavior.
284  * @param int $userId the ID of the user to which the item is assigned.
285  * @param CAuthItem $parent the parent of the given item.
286  *
287  * @return mixed the item or items with the behavior attached.
288  */
289  public function attachAuthItemBehavior($items, $userId = null, CAuthItem $parent = null)
290  {
291  // We have a single item.
292  if ($items instanceof CAuthItem) {
293  $items->attachBehavior('rights', new RAuthItemBehavior($userId, $parent));
294  // We have multiple items.
295  } else if ($items===(array)$items) {
296  foreach ($items as $item) {
297  $item->attachBehavior('rights', new RAuthItemBehavior($userId, $parent));
298  }
299  }
300 
301  return $items;
302  }
303 
304  /**
305  * Returns the users with superuser privileges.
306  *
307  * @throws CHttpException
308  * @return array
309  */
310  public function getSuperusers()
311  {
312  $assignments = $this->_authManager->getAssignmentsByItemName(Rights::module()->superuserName);
313 
314  $userIdList = array();
315  foreach ($assignments as $userId => $assignment) {
316  $userIdList[] = $userId;
317  }
318 
319  $criteria = new CDbCriteria();
320  $criteria->addInCondition(Rights::module()->userIdColumn, $userIdList);
321 
322  $userClass = Rights::module()->userClass;
323  $users = CActiveRecord::model($userClass)->findAll($criteria);
324  $users = $this->attachUserBehavior($users);
325 
326  $superusers = array();
327  foreach ($users as $user) {
328  $superusers[] = $user->email;
329  }
330 
331  // Make sure that we have superusers, otherwise we would allow full access to Rights
332  // if there for some reason is not any superusers.
333  if ($superusers===array()) {
334  throw new CHttpException(403, Rights::t('core', 'There must be at least one superuser!'));
335  }
336 
337  return $superusers;
338  }
339 
340  /**
341  * Attaches the rights user behavior to the given users.
342  *
343  * @param mixed $users the user or users to which attach the behavior.
344  *
345  * @return mixed the user or users with the behavior attached.
346  */
347  public function attachUserBehavior($users)
348  {
349  $userClass = Rights::module()->userClass;
350 
351  // We have a single user.
352  if ($users instanceof $userClass) {
353  $users->attachBehavior('rights', new RUserBehavior);
354  } // We have multiple user.
355  else if ($users===(array)$users) {
356  foreach ($users as $user) {
357  $user->attachBehavior('rights', new RUserBehavior);
358  }
359  }
360 
361  return $users;
362  }
363 
364  /**
365  * Returns whether the user is a superuser.
366  *
367  * @param integer $userId the id of the user to do the check for.
368  *
369  * @return boolean whether the user is a superuser.
370  */
371  public function isSuperuser($userId)
372  {
373  $assignments = $this->_authManager->getAuthAssignments($userId);
374  return isset($assignments[$this->superuserName]);
375  }
376 
377  /**
378  * Returns the permissions for a specific authorization item.
379  *
380  * @param string $itemName the name of the item for which to get permissions. Defaults to null,
381  * meaning that the full permission tree is returned.
382  *
383  * @return the permission tree.
384  */
385  public function getPermissions($itemName = null)
386  {
387  $permissions = array();
388 
389  if ($itemName!==null) {
390  $item = $this->_authManager->getAuthItem($itemName);
391  $permissions = $this->getPermissionsRecursive($item);
392  } else {
393  foreach ($this->getRoles() as $roleName => $role) {
394  $permissions[$roleName] = $this->getPermissionsRecursive($role);
395  }
396  }
397 
398  return $permissions;
399  }
400 
401  /**
402  * Returns the permissions for a specific authorization item recursively.
403  *
404  * @param CAuthItem $item the item for which to get permissions.
405  *
406  * @return array the section of the permissions tree.
407  */
408  private function getPermissionsRecursive(CAuthItem $item)
409  {
410  $permissions = array();
411  foreach ($item->getChildren() as $childName => $child) {
412  $permissions[$childName] = array();
413  if (($grandChildren = $this->getPermissionsRecursive($child))!==array()) {
414  $permissions[$childName] = $grandChildren;
415  }
416  }
417 
418  return $permissions;
419  }
420 
421  /**
422  * Returns the permission type for an authorization item.
423  *
424  * @param string $itemName the name of the item to check permission for.
425  * @param string $parentName the name of the item in which permissions to look.
426  * @param array $permissions the permissions.
427  *
428  * @return integer the permission type (0: None, 1: Direct, 2: Inherited).
429  */
430  public function hasPermission($itemName, $parentName = null, $permissions = array())
431  {
432  if ($parentName!==null) {
433  if ($parentName===$this->superuserName) {
434  return 1;
435  }
436 
437  $permissions = $this->getPermissions($parentName);
438  }
439 
440  if (isset($permissions[$itemName])) {
441  return 1;
442  }
443 
444  foreach ($permissions as $children) {
445  if ($children!==array()) {
446  if ($this->hasPermission($itemName, null, $children) > 0) {
447  return 2;
448  }
449  }
450  }
451 
452  return 0;
453  }
454 
455  /**
456  * Tries to sanitize code to make it safe for execution.
457  *
458  * @param string $code the code to be execute.
459  *
460  * @return mixed the return value of eval() or null if the code was unsafe to execute.
461  */
462  protected function sanitizeExpression($code)
463  {
464  // Language consturcts.
465  $languageConstructs = array(
466  'echo',
467  'empty',
468  'isset',
469  'unset',
470  'exit',
471  'die',
472  'include',
473  'include_once',
474  'require',
475  'require_once',
476  );
477 
478  // Loop through the language constructs.
479  foreach ($languageConstructs as $lc) {
480  if (preg_match('/'.$lc.'\ *\(?\ *[\"\']+/', $code) > 0) {
481  return null;
482  }
483  } // Language construct found, not safe for eval.
484 
485  // Get a list of all defined functions
486  $definedFunctions = get_defined_functions();
487  $functions = array_merge($definedFunctions['internal'], $definedFunctions['user']);
488 
489  // Loop through the functions and check the code for function calls.
490  // Append a '(' to the functions to avoid confusion between e.g. array() and array_merge().
491  foreach ($functions as $f) {
492  if (preg_match('/'.$f.'\ *\({1}/', $code) > 0) {
493  return null;
494  }
495  } // Function call found, not safe for eval.
496 
497  // Evaluate the safer code
498  $result = @eval($code);
499 
500  // Return the evaluated code or null if the result was false.
501  return $result!==false ? $result : null;
502  }
503 
504  /**
505  * @return RAuthManager the authorization manager.
506  */
507  public function getAuthManager()
508  {
509  return $this->_authManager;
510  }
511 }