[ Index ]

PHP Cross Reference of ACL Module

title

Body

[close]

/models/ -> RestrictedActiveRecord.php (source)

   1  <?php
   2  
   3  /**

   4   * RestrictedActiveRecord Class File

   5   * This class serves as a base-class for all the objects which have to control 

   6   * their access

   7   *

   8   * @author dispy <dispyfree@googlemail.com>

   9   * @license LGPLv2

  10   * @package acl.base

  11   */
  12  
  13  /**

  14   * This class is intended as a base for objects which have restrictions on their access

  15   * It automatically checks, if the current user has the permissions to commit the regular CRUD-tasks

  16   */
  17  abstract class RestrictedActiveRecord extends CActiveRecord{
  18      
  19      /**

  20       * This is used to temporarily disable the Access  Check

  21       * This may be needed if for example the user is a guest and has therefore no update-rights (he doesn't have an ARO representation) on his own objects

  22       * @var boolean

  23       */
  24      public static $byPassCheck = false;
  25      
  26      /**

  27       * This variable can be used to access the resource in attendance of another ARO-collection than the current user

  28       * default is to NULL which means that the user denoted by Yii::app()->user is used for access checking

  29       * @var mixed

  30       */
  31      public static $inAttendance = NULL;
  32      
  33      /**

  34       * @var string  The model to use for automatic access checing (as Aro-Collection). Default: "User" 

  35       */
  36      public static $model = 'User';
  37      
  38      /**

  39       * This contains all possible actions for the objects of this class. 

  40       * All other actions will never be granted and always denied upon inquiry. 

  41       * Example: array('update', 'read') << all other action are not available

  42       * @var array[string]

  43       */
  44      public static $possibleActions = NULL;
  45      
  46      public static $defaultOptions = array(
  47          'disableInheritance' => false, // If true, no aco will inherit the permissions of it's parent
  48      );
  49      
  50      /**

  51       * The following functions generates the CDbCriteria necessary to filter all accessable rows

  52       * The CDbCriteria is solely passsed to the wrapped methods

  53       * @param sql $conditions the conditions being passed to the real method

  54       * @param array $params the params being passed to the real method

  55       * @param   array   $options    options to be used by the method itself (keys: disableInheritance)

  56       * @return CDbCriteria the criteria assuring that the user only gets what he has access to

  57       */
  58      protected function generateAccessCheck($conditions = '', $params = array(), $options = array()){
  59          if(is_object($conditions) && get_class($conditions) == 'CDbCriteria'){
  60              $criteria = $conditions;
  61          }
  62          else{
  63              $criteria = new CDbCriteria;
  64              $criteria->mergeWith(array(
  65                  'condition' => $conditions,
  66                  'params'    => $params
  67              ));
  68          }
  69          
  70          //If he's generally allowed, don't filter at all

  71          if(self::mayGenerally(get_class($this), 'read'))
  72                  return $criteria;
  73          
  74          $options = array_merge(RestrictedActiveRecord::$defaultOptions, $options);
  75          
  76          //If the check is bypassed, return criteria without check

  77          if(RestrictedActiveRecord::$byPassCheck)
  78              return $criteria;
  79          
  80          $criteria->distinct = true; //Important: there can be multiple locations which grant permission

  81              
  82              //Inner join to get the collection associated with this content

  83              $acoClass = Strategy::getClass('Aco');
  84              $collection = 'INNER JOIN `'.$acoClass::model()->tableName().'` AS acoC ON acoC.model = :RAR_model AND acoC.foreign_key = t.id';
  85                  $criteria->params[':RAR_model'] = get_class($this);
  86              
  87              //Inner join to the associated aco-nodes themselves to get the positions

  88              $acoNodeClass = Strategy::getClass('AcoNode');
  89              $nodes = ' INNER JOIN `'.$acoNodeClass::model()->tableName().'` AS aco ON aco.collection_id = acoC.id';
  90              
  91              //But before: fetch the positions of the current user

  92              $aroClass = Strategy::getClass('Aro');
  93              $user = RestrictedActiveRecord::getUser();
  94              $aro = $aroClass::model()->find('model = :model AND foreign_key = :foreign_key', 
  95                      array(':model'=> static::$model, ':foreign_key' => $user->id));
  96              
  97              //If we are nobody... we are a guest^^

  98              $guest = Strategy::get('guestGroup');
  99              if(!$aro && $guest){
 100                   $aro = $aroClass::model()->find('alias = :alias', 
 101                      array(':alias' => $guest));
 102                   
 103                   //If there's no guest group... we are nobody and we may nothing ;)

 104                   if(!$aro)
 105                       return array();
 106              }
 107                 
 108              
 109              $aroPositions = $aro->fetchComprisedPositions();
 110              $aroPositionCheck = $aro->addPositionCheck($aroPositions, "aro", "map"); 
 111              
 112              //Get our action :)

 113              $action = Action::model()->find('name = :name', array(':name' => 'read'));
 114              
 115              if($action === NULL)
 116                  throw new RuntimeException('Unable to find action read');
 117              
 118              //Now, join connecting table

 119              $acoCondition = $acoClass::buildTreeQueryCondition(
 120                      array('table' => 'aco'),
 121                      array('table' => 'map', 'field' => 'aco'),
 122                      $options['disableInheritance']
 123                      );
 124              $connection = ' INNER JOIN `'.Permission::model()->tableName().'` AS map ON '.$acoCondition.' AND '.$aroPositionCheck.' AND map.action_id = :acl_action_id';
 125              $criteria->params[':acl_action_id'] = $action->id;
 126          
 127         $joins = array($collection, $nodes, $connection);
 128         
 129         foreach($joins as $join){
 130             $criteria->mergeWith(array('join' => $join), true);
 131         }
 132         
 133         
 134         return $criteria;
 135      }
 136      
 137      
 138      public function find($conditions = '', $params = array()){
 139         return parent::find($this->generateAccessCheck($conditions, $params));
 140      }
 141      
 142      public function findByAttributes($attributes, $conditions = '', $params = array()){
 143         return parent::findByAttributes($attributes, $this->generateAccessCheck($conditions, $params));
 144      }
 145      
 146      public function findByPk($pk, $conditions = '', $params = array()){
 147         return parent::findByPk($pk, $this->generateAccessCheck($conditions, $params));
 148      }
 149      
 150      public function findBySQL($sql, $params = array()){
 151         return parent::find($this->generateAccessCheck($sql, $params));
 152      }
 153      
 154      
 155      public function findAll($conditions = '', $params = array()){
 156         return parent::findAll($this->generateAccessCheck($conditions, $params));
 157      }
 158      
 159      public function findAllByAttributes($attributes, $conditions = '', $params = array()){
 160         return parent::findAllByAttributes($attributes, $this->generateAccessCheck($conditions, $params));
 161      }
 162      
 163      public function findAllByPk($pk, $conditions = '', $params = array()){
 164         return parent::findAllByPk($pk, $this->generateAccessCheck($conditions, $params));
 165      }
 166      
 167      public function findAllBySQL($sql, $params = array()){
 168         return parent::findAll($this->generateAccessCheck($sql, $params));
 169      }
 170      
 171      
 172      /**

 173       * Gets the Aros who are directly (no inheritance!) permitted to perform

 174       * one of the specified actions on this object

 175       * @param mixed $actions the actions to be considered

 176       * @return array All of the objects which have one of the permissions

 177       */
 178      public function getDirectlyPermitted($actions = '*'){
 179          //First, fetch all of the action Ids

 180          $actions = Action::translateActions($this, $actions);
 181          $actionCondition = Util::generateInStatement($actions);
 182          $actions = Action::model()->findAll('name '.$actionCondition);
 183          
 184          $actionIds = array();
 185          foreach($actions as $action){
 186              $actionIds[] = $action->id;
 187          }
 188          $actionIdCondition = Util::generateInStatement($actionIds);
 189          
 190          //Get the associated Aco first

 191          $aco = AclObject::loadObjectStatic($this, 'Aco');
 192          //Fetch all of the own positions and build condition

 193          $positions = $aco->fetchComprisedPositions();
 194          $acoCondition = Util::generateInStatement($positions);
 195          
 196          $aroNodeClass   = Strategy::getClass('AroNode');
 197          
 198          $rGroupTable    = RGroup::model()->tableName();
 199          $nodeTable      = $aroNodeClass::model()->tableName();
 200          $permTable      = Permission::model()->tableName();
 201          return Yii::app()->db->createCommand()
 202                  ->selectDistinct('t.id AS collection_id, t.foreign_key, t.model, p.action_id')
 203                  ->from($rGroupTable.' t')
 204                  ->join($nodeTable.' n', 'n.collection_id = t.id')
 205                  ->join($permTable.' p', 
 206                          'p.aro_id = n.id AND p.aco_path '.$acoCondition.' AND p.action_id '. $actionIdCondition)
 207                  ->queryAll()
 208                  ;
 209      }
 210      
 211      /**

 212       * This method checks whether the user has the right to update the current record

 213       * By default, it's always allowed to create a new object. This object is automatically assigned to the user who created it with full permissions

 214       */
 215      public function beforeSave(){
 216          parent::beforeSave();
 217          $aro = self::getUser();
 218          
 219          //The Record is updated

 220          if(!$this->isNewRecord){
 221              
 222              if(!$aro->may($this, 'update'))
 223                  throw new RuntimeException('You are not allowed to update this record');            
 224          }else{
 225              if(!$aro->may(get_class($this), 'create'))
 226                      throw new RuntimeException('You are not allowed to create this object');
 227          }
 228          
 229          return true;
 230      }
 231      
 232      /**

 233       * This method checks whether the user has the right to delete the current record

 234       * 

 235       */
 236      public function beforeDelete(){
 237          parent::beforeDelete();
 238          
 239          if(self::mayGenerally(get_class($this), 'delete'))
 240                  return true;
 241          
 242          $aro = self::getUser();
 243          if(!$aro->may($this, 'delete'))
 244                  throw new RuntimeException('You are not allowed to delete this record');
 245          
 246          //Ok he has the right to do that - remove all the ACL-objects associated with this object

 247          $class = Strategy::getClass('Aco');
 248          $aco = $class::model()->find('model = :model AND foreign_key = :key', array(':model' => get_class($this), ':key' => $this->id));
 249          if(!$aco)
 250              throw new RuntimeException('No associated Aco!');
 251          
 252          if(!$aco->delete())
 253              throw new RuntimeException('Unable to delete associated Aco');
 254          
 255          return true;
 256      }
 257      
 258      
 259      /**

 260       * This method takes care to assign individual rights to newly created objects

 261       * 

 262       * @param CEvent $evt 

 263       */
 264      public function afterSave(){
 265          parent::afterSave();
 266          if($this->isNewRecord){
 267              $aro = self::getUser();
 268              //As the object is newly created, it needs a representation

 269              //If strict mode is disabled, this is not necessary

 270              $class = Strategy::getClass('Aco');
 271              $aco = new $class();
 272              $aco->model = get_class($this);
 273              $aco->foreign_key = $this->getPrimaryKey();
 274              
 275              if(!$aco->save()){
 276                  throw new RuntimeException('Unable to create corresponding Aco for new '.get_class($this));
 277              }
 278              
 279              $aro->grant($aco, self::getAutoPermissions($this), true);
 280          }
 281      }
 282      
 283      public static function getAutoPermissions($obj){
 284          if(!isset($obj->autoPermissions))
 285              return Strategy::get('autoPermissions');
 286          return $obj->autoPermissions;
 287      }
 288      
 289      
 290      /**

 291       * Checks whether the current ARO has the given permission on this object

 292       * @param string $permission 

 293       */
 294      public function grants($permission){
 295          $aro = self::getUser();
 296          return $aro->may($this, $permission);
 297      }
 298      
 299      /**

 300       * Fetches the Access Request-Object to use (either the current user 

 301       * or an object from self::inAttendance.

 302       * @see inAttendance

 303       * @return AclObject

 304       * @throws RuntimeException 

 305       */
 306      public static function getUser(){
 307          
 308          if(self::$inAttendance !== NULL)
 309              return self::$inAttendance;
 310          
 311          $user = Yii::app()->user;
 312          $class = Strategy::getClass('Aro');
 313          $aro = $class::model()->find('model = :model AND foreign_key = :foreign_key', 
 314                  array('model' => static::$model, 'foreign_key' => $user->id));
 315          if(!$aro)
 316              throw new RuntimeException('Invalid Aro');
 317          return $aro;
 318      }
 319      
 320      /**

 321       * Checks whether the user is generally allowed to perform the given permission(s) on the given object

 322       * @see "enableGeneralPermissions" config.php of the currenctly active strategy

 323       * @param mixed $aco the object to perform the action on (either a string or a class)

 324       * @param mixed $perm“the permission to perform

 325       */
 326      public static function mayGenerally($aco, $perm){
 327          //Return always false if general permissions are disabled

 328          if(Strategy::get('allowGeneralPermissions') == false)
 329              return false;
 330          
 331          //If the permission is generally granted on all objects

 332          if(!is_array($perm)){
 333              return in_array($perm, Strategy::get('generalPermissions'));
 334          }
 335          else{
 336              //Only if all of the given actions are generally granted this returns true

 337              $generallyGranted = true;
 338              foreach($perm as $permission){
 339                  if(!in_array($permission, Strategy::get('generalPermissions')))
 340                          $generallyGranted = false;
 341              }
 342              if($generallyGranted)
 343                  return true;
 344          }
 345          
 346          //We want to act on the general model, e.g. "Post" (using Aliases)

 347          if(!is_string($aco)){
 348              if($aco instanceof CActiveRecord)
 349                  $aco = get_class($aco);
 350              else
 351                  throw new RuntimeException('Invalid Aco-Argument');
 352          }
 353          return self::getUser()->may($aco, $perm);
 354      }
 355      
 356      /**

 357       * Defining them statically is quite a mess, but that's what PHP is. 

 358       * @see PmAco

 359       */
 360      
 361      /**

 362       * Checks if the given permission is granted - includes business-rules

 363       * @param string    $condition the query condition

 364       * @param array     $params  the attached parameters

 365       * @param Object    $originalAco the passed object (aco)

 366       */
 367      public static function checkBirPermission($condition, $params){
 368          //First fetch all permission which _could_ grant the right

 369          $permissions = self::getPermissionsWithBiz($condition, $params);
 370  
 371          //Now, search them. 

 372          foreach($permissions as $mode => $mode_permissions){
 373              foreach($mode_permissions as $permission){
 374                  if(self::areBusinessRulesFulfilled($mode, $permission))
 375                      return true;
 376              }
 377          }
 378          return false;
 379      }
 380      
 381      /**

 382       * Finds all permissions including their associated objects (nodes and collections)

 383       * Uses the configuration to determine in which direction(s) to fetch objects

 384       * @param array $conditions ("aroCondition" and "acoCondition")

 385       * @param array $params

 386       * @return array ["aro" =>Permissions, "aco" => Permissions]  

 387       * Where aro and aco indicate in which direction to check for business rules

 388       */
 389      protected static function getPermissionsWithBiz($conditions, $params){
 390          //Order by the alias, because if we don't have an alias, we don't need to 

 391          //check any business-rule!

 392          $mode = Strategy::get('lookupBusinessRules');
 393          
 394          //We need to step through each mode because a lookup can only be

 395          //applied in one direction at once (=> merge the results)

 396          $modes = $mode == 'both' ? array('aro', 'aco') : array($mode);
 397          $finishedPermissions = array();
 398          
 399          foreach($modes as $mode){
 400              $order = NULL;
 401              //Now, set the relations accordingly

 402              //Depending on what business-rules are enabled, we need to exclude

 403              //some paths (and thus conditions)

 404              switch($mode){
 405                  case 'aro':
 406                      unset($conditions['aroCondition']);
 407                      break;
 408                  case 'aco':
 409                      unset($conditions['acoCondition']);
 410                      break;
 411                  default:
 412                      throw new RuntimeException('Specified invalid Business-Rule mode');
 413              }
 414              //We need the actions anyway

 415              $with = array(
 416                  'action',
 417                  'aroNode.aro',
 418                  'acoNode.aco'
 419              );
 420              $order = 'aro.alias ASC, aco.alias ASC';
 421  
 422              //Finally, build the condition

 423              $condition = " action_id = :action_id ";
 424              foreach($conditions as $tmp_condition){
 425                  $condition.= ' AND '.$tmp_condition;
 426              }
 427  
 428              //Finally, exclude all conditions in which neither alias is set

 429              $condition.=' AND (aco.alias != NULL OR aro.alias != NULL )';
 430              
 431              $permissions = Permission::model()->with($with)->findAll(
 432                  array('condition' => $condition, 'params' => $params, 'order' => $order));
 433              $finishedPermissions[$mode] = $permissions;
 434          }
 435          
 436          return $finishedPermissions;
 437      }
 438      
 439      /**

 440       * Checks whether the business-rules attached to this permission are fulfilled

 441       * @param string    $mode (either "aro" or "aco"

 442       * @param Permission $permission  the permission to check

 443       * @return boolean  true if they are fulfilled, false otherwise

 444       */
 445      protected static function areBusinessRulesFulfilled($mode, $permission) {
 446          $suffix = 'Node';
 447          $node = $mode . $suffix;
 448          //Is the direction loaded at all?

 449          if (isset($permission->{$node})) {
 450              $collection = $permission->{$node}->{$mode};
 451              //If no alias is used, we don't care anyway

 452              if (!$collection->alias)
 453                  return false;
 454              
 455              //We - we have to check

 456              $rule = 'is' . $collection->alias;
 457  
 458              //Fetch Objects if possible

 459              $aro = Util::getByIdentifier($permission->aroNode->aro);
 460              $aro = !$aro ? $permission->aroNode->aro : $aro;
 461  
 462              $aco = Util::getByidentifier($permission->acoNode->aco);
 463              $aco = !$aco ? $permission->acoNode->aco : $aco;
 464  
 465              //Finally, check against the business-rule

 466              return BusinessRules::fulfillsBusinessRule($rule, $aro, $aco, $permission->action);
 467          }
 468          //Nothing to check - check nothing ^^

 469          return true;
 470      }
 471      
 472      
 473  }
 474  ?>


Generated: Sun Jul 1 19:24:45 2012 Cross-referenced by PHPXref 0.7.1