[ Index ] |
PHP Cross Reference of ACL Module |
[Summary view] [Print] [Text view]
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 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Jul 1 19:24:45 2012 | Cross-referenced by PHPXref 0.7.1 |