Account Suspended
Account Suspended
This Account has been suspended.
Contact your hosting provider for more information.
 All Data Structures Functions Variables Pages
SecurableItem.php
1 <?php
2  /*********************************************************************************
3  * Zurmo is a customer relationship management program developed by
4  * Zurmo, Inc. Copyright (C) 2017 Zurmo Inc.
5  *
6  * Zurmo is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU Affero General Public License version 3 as published by the
8  * Free Software Foundation with the addition of the following permission added
9  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
10  * IN WHICH THE COPYRIGHT IS OWNED BY ZURMO, ZURMO DISCLAIMS THE WARRANTY
11  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
12  *
13  * Zurmo is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Affero General Public License along with
19  * this program; if not, see http://www.gnu.org/licenses or write to the Free
20  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21  * 02110-1301 USA.
22  *
23  * You can contact Zurmo, Inc. with a mailing address at 27 North Wacker Drive
24  * Suite 370 Chicago, IL 60606. or at email address contact@zurmo.com.
25  *
26  * The interactive user interfaces in original and modified versions
27  * of this program must display Appropriate Legal Notices, as required under
28  * Section 5 of the GNU Affero General Public License version 3.
29  *
30  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
31  * these Appropriate Legal Notices must retain the display of the Zurmo
32  * logo and Zurmo copyright notice. If the display of the logo is not reasonably
33  * feasible for technical reasons, the Appropriate Legal Notices must display the words
34  * "Copyright Zurmo Inc. 2017. All rights reserved".
35  ********************************************************************************/
36 
37  class SecurableItem extends Item
38  {
45  private $_explicitReadWriteModelPermissionsForWorkflow;
46 
51  private $_permitablesToAttachAfterSave = array();
52 
56  private $permissionsChanged = false;
57 
62  private $_permitablesToDetachAfterSave = array();
63 
64  public function getExplicitReadWriteModelPermissionsForWorkflow()
65  {
66  return $this->_explicitReadWriteModelPermissionsForWorkflow;
67  }
68 
69  public function setExplicitReadWriteModelPermissionsForWorkflow(ExplicitReadWriteModelPermissions $permissions)
70  {
71  $this->_explicitReadWriteModelPermissionsForWorkflow = $permissions;
72  }
73 
74  public function clearExplicitReadWriteModelPermissionsForWorkflow()
75  {
76  $this->_explicitReadWriteModelPermissionsForWorkflow = null;
77  }
78 
79  public function getEffectivePermissions($permitable = null)
80  {
81  list($allowPermissions, $denyPermissions) = $this->getActualPermissions($permitable);
82  $permissions = $allowPermissions & ~$denyPermissions;
83  assert("($permissions & ~Permission::ALL) == 0");
84  return $permissions;
85  }
86 
92  public function getActualPermissions($permitable = null)
93  {
94  assert('$permitable === null || $permitable instanceof Permitable');
95  if ($permitable === null)
96  {
97  $permitable = Yii::app()->user->userModel;
98  if (!$permitable instanceof User)
99  {
100  throw new NoCurrentUserSecurityException();
101  }
102  }
103  if (!SECURITY_OPTIMIZED || $this->processGetActualPermissionsAsNonOptimized())
104  {
105  // The slow way will remain here as documentation
106  // for what the optimized way is doing.
107  $allowPermissions = Permission::NONE;
108  $denyPermissions = Permission::NONE;
109  if (Group::getByName(Group::SUPER_ADMINISTRATORS_GROUP_NAME)->contains($permitable))
110  {
111  $allowPermissions = Permission::ALL;
112  }
113  else
114  {
115  foreach ($this->unrestrictedGet('permissions') as $permission)
116  {
117  $effectivePermissions = $permission->getEffectivePermissions($permitable);
118  if ($permission->type == Permission::ALLOW)
119  {
120  $allowPermissions |= $effectivePermissions;
121  }
122  else
123  {
124  $denyPermissions |= $effectivePermissions;
125  }
126  }
127  $allowPermissions |= $this->getPropagatedActualAllowPermissions($permitable);
128  if (!($this instanceof NamedSecurableItem))
129  {
130  foreach (array(get_class($this), static::getModuleClassName()) as $securableItemName)
131  {
132  try
133  {
134  $securableType = NamedSecurableItem::getByName($securableItemName);
135  $typeAllowPermissions = Permission::NONE;
136  $typeDenyPermissions = Permission::NONE;
137  foreach ($securableType->unrestrictedGet('permissions') as $permission)
138  {
139  $effectivePermissions = $permission->getEffectivePermissions($permitable);
140  if ($permission->type == Permission::ALLOW)
141  {
142  $typeAllowPermissions |= $effectivePermissions;
143  }
144  else
145  {
146  $typeDenyPermissions |= $effectivePermissions;
147  }
148  // We shouldn't see something that isn't owned having CHANGE_OWNER.
149  // assert('$typeAllowPermissions & Permission::CHANGE_OWNER == Permission::NONE');
150  }
151  $allowPermissions |= $typeAllowPermissions;
152  $denyPermissions |= $typeDenyPermissions;
153  }
154  catch (NotFoundException $e)
155  {
156  }
157  }
158  }
159  }
160  }
161  else
162  {
163  try
164  {
165  $combinedPermissions = PermissionsCache::getCombinedPermissions($this, $permitable);
166  }
167  catch (NotFoundException $e)
168  {
169  $securableItemId = $this ->getClassId('SecurableItem');
170  $permitableId = $permitable->getClassId('Permitable');
171  // Optimizations work on the database,
172  // anything not saved will not work.
173  assert('$permitableId > 0');
174  $className = get_class($this);
175  $moduleName = static::getModuleClassName();
177  $combinedPermissions = intval(ZurmoDatabaseCompatibilityUtil::
178  callFunction("get_securableitem_actual_permissions_for_permitable($securableItemId, $permitableId, '$className', '$moduleName', $cachingOn)"));
179  PermissionsCache::cacheCombinedPermissions($this, $permitable, $combinedPermissions);
180  }
181  $allowPermissions = ($combinedPermissions >> 8) & Permission::ALL;
182  $denyPermissions = $combinedPermissions & Permission::ALL;
183  }
184  assert("($allowPermissions & ~Permission::ALL) == 0");
185  assert("($denyPermissions & ~Permission::ALL) == 0");
186  return array($allowPermissions, $denyPermissions);
187  }
188 
194  {
195  return false;
196  }
197 
198  public function getPropagatedActualAllowPermissions(Permitable $permitable)
199  {
200  if ($permitable instanceof User)
201  {
202  $allowPermissions = Permission::NONE;
203  $descendentRoles = $this->getAllDescendentRoles($permitable->role);
204  foreach ($descendentRoles as $role)
205  {
206  $allowPermissions |= $this->recursiveGetPropagatedAllowPermissions($role);
207  }
208  return $allowPermissions;
209  }
210  else
211  {
212  return Permission::NONE;
213  }
214  }
215 
216  protected function recursiveGetPropagatedAllowPermissions($role)
217  {
218  if (!SECURITY_OPTIMIZED || $this->processGetActualPermissionsAsNonOptimized())
219  {
220  // The slow way will remain here as documentation
221  // for what the optimized way is doing.
222  $propagatedPermissions = Permission::NONE;
223  foreach ($role->users as $userInRole)
224  {
225  $propagatedPermissions |= $this->getEffectivePermissions($userInRole) ;
226  }
227  return $propagatedPermissions;
228  }
229  else
230  {
231  // It should never get here because the optimized version
232  // of getActualPermissions will call
233  // get_securableitem_propagated_allow_permissions_for_permitable.
234  throw new NotSupportedException();
235  }
236  }
237 
238  protected function getAllDescendentRoles($role)
239  {
240  $descendentRoles = array();
241  if (count($role->roles) > 0)
242  {
243  foreach ($role->roles as $childRole)
244  {
245  $descendentRoles[] = $childRole;
246  $descendentRoles = array_merge($descendentRoles,
247  $this->getAllDescendentRoles($childRole));
248  }
249  }
250  return $descendentRoles;
251  }
252 
253  public function getExplicitActualPermissions($permitable = null)
254  {
255  assert('$permitable === null || $permitable instanceof Permitable');
256  if ($permitable === null)
257  {
258  $permitable = Yii::app()->user->userModel;
259  if (!$permitable instanceof User)
260  {
261  throw new NoCurrentUserSecurityException();
262  }
263  }
264  $allowPermissions = Permission::NONE;
265  $denyPermissions = Permission::NONE;
266  foreach ($this->unrestrictedGet('permissions') as $permission)
267  {
268  $explicitPermissions = $permission->getExplicitPermissions($permitable);
269  if ($permission->type == Permission::ALLOW)
270  {
271  $allowPermissions |= $explicitPermissions;
272  }
273  else
274  {
275  $denyPermissions |= $explicitPermissions;
276  }
277  }
278  assert("($allowPermissions & ~Permission::ALL) == 0");
279  assert("($denyPermissions & ~Permission::ALL) == 0");
280  return array($allowPermissions, $denyPermissions);
281  }
282 
283  public function getInheritedActualPermissions($permitable = null)
284  {
285  assert('$permitable === null || $permitable instanceof Permitable');
286  if ($permitable === null)
287  {
288  $permitable = Yii::app()->user->userModel;
289  if (!$permitable instanceof User)
290  {
291  throw new NoCurrentUserSecurityException();
292  }
293  }
294  $allowPermissions = Permission::NONE;
295  $denyPermissions = Permission::NONE;
296  foreach ($this->unrestrictedGet('permissions') as $permission)
297  {
298  $inheritedPermissions = $permission->getInheritedPermissions($permitable);
299  if ($permission->type == Permission::ALLOW)
300  {
301  $allowPermissions |= $inheritedPermissions;
302  }
303  else
304  {
305  $denyPermissions |= $inheritedPermissions;
306  }
307  }
308  if (!($this instanceof NamedSecurableItem))
309  {
310  foreach (array(get_class($this), static::getModuleClassName()) as $securableItemName)
311  {
312  try
313  {
314  $securableType = NamedSecurableItem::getByName($securableItemName);
315  $typeAllowPermissions = Permission::NONE;
316  $typeDenyPermissions = Permission::NONE;
317  foreach ($securableType->permissions as $permission)
318  {
319  $inheritedPermissions = $permission->getInheritedPermissions($permitable);
320  if ($permission->type == Permission::ALLOW)
321  {
322  $typeAllowPermissions |= $inheritedPermissions;
323  }
324  else
325  {
326  $typeDenyPermissions |= $inheritedPermissions;
327  }
328  }
329  $allowPermissions |= $typeAllowPermissions;
330  $denyPermissions |= $typeDenyPermissions;
331  }
332  catch (NotFoundException $e)
333  {
334  }
335  }
336  }
337  assert("($allowPermissions & ~Permission::ALL) == 0");
338  assert("($denyPermissions & ~Permission::ALL) == 0");
339  return array($allowPermissions, $denyPermissions);
340  }
341 
349  public function addPermissions(Permitable $permitable, $permissions, $type = Permission::ALLOW)
350  {
351  assert('is_int($permissions)');
352  assert("($permissions & ~Permission::ALL) == 0");
353  assert('$permissions != Permission::NONE');
354  assert('in_array($type, array(Permission::ALLOW, Permission::DENY))');
355  $this->checkPermissionsHasAnyOf(Permission::CHANGE_PERMISSIONS);
356  if ($this instanceof NamedSecurableItem)
357  {
358  PermissionsCache::forgetAll();
359  AllPermissionsOptimizationCache::forgetAll();
360  }
361  else
362  {
363  PermissionsCache::forgetSecurableItem($this);
365  }
366  $found = false;
367  foreach ($this->permissions as $permission)
368  {
369  if ($permission->permitable->isSame($permitable) &&
370  $permission->type == $type)
371  {
372  $permission->permissions |= $permissions;
373  $found = true;
374  break;
375  }
376  }
377  if (!$found)
378  {
379  $permission = new Permission();
380  $permission->permitable = $permitable;
381  $permission->type = $type;
382  $permission->permissions = $permissions;
383  $this->permissions->add($permission);
384  $this->permissionsChanged = true;
385  return true;
386  }
387  else
388  {
389  return false;
390  }
391  }
392 
398  public function removePermissions(Permitable $permitable, $permissions = Permission::ALL, $type = Permission::ALLOW_DENY)
399  {
400  assert('is_int($permissions)');
401  assert("($permissions & ~Permission::ALL) == 0");
402  assert('$permissions != Permission::NONE');
403  assert('in_array($type, array(Permission::ALLOW, Permission::DENY, Permission::ALLOW_DENY))');
404  $this->checkPermissionsHasAnyOf(Permission::CHANGE_PERMISSIONS);
405  if ($this instanceof NamedSecurableItem)
406  {
407  PermissionsCache::forgetAll();
408  AllPermissionsOptimizationCache::forgetAll();
409  }
410  else
411  {
412  PermissionsCache::forgetSecurableItem($this);
414  }
415  foreach ($this->permissions as $permission)
416  {
417  if ($permission->permitable->isSame($permitable) &&
418  ($permission->type == $type ||
419  $type == Permission::ALLOW_DENY))
420  {
421  $permission->permissions &= ~$permissions;
422  if ($permission->permissions == Permission::NONE)
423  {
424  $this->permissions->remove($permission);
425  }
426  }
427  }
428  if ($this instanceof NamedSecurableItem)
429  {
430  PermissionsCache::forgetAll();
431  AllPermissionsOptimizationCache::forgetAll();
432  }
433  else
434  {
435  PermissionsCache::forgetSecurableItem($this);
437  }
438  $this->permissionsChanged = true;
439  }
440 
441  public function removeAllPermissions()
442  {
443  $this->checkPermissionsHasAnyOf(Permission::CHANGE_PERMISSIONS);
444  PermissionsCache::forgetAll();
445  AllPermissionsOptimizationCache::forgetAll();
446  $this->permissions->removeAll();
447  }
448 
449  public function __get($attributeName)
450  {
451  if (!$this->isSaving &&
452  !$this->isSetting &&
453  !$this->isValidating &&
454  // Anyone can get the id and owner, createdByUser, and modifiedByUser anytime.
455  !in_array($attributeName, array('id', 'owner', 'createdByUser', 'modifiedByUser')))
456  {
457  $this->checkPermissionsHasAnyOf(Permission::READ);
458  }
459  return parent::__get($attributeName);
460  }
461 
462  public function __set($attributeName, $value)
463  {
464  if ($attributeName == 'owner')
465  {
466  $this->checkPermissionsHasAnyOf(Permission::CHANGE_OWNER);
467  }
468  elseif ($attributeName == 'permissions')
469  {
470  $this->checkPermissionsHasAnyOf(Permission::CHANGE_PERMISSIONS);
471  }
472  else
473  {
474  $this->checkPermissionsHasAnyOf(Permission::WRITE);
475  }
476  parent::__set($attributeName, $value);
477  }
478 
479  public function delete()
480  {
481  $this->checkPermissionsHasAnyOf(Permission::DELETE);
482  return parent::delete();
483  }
484 
489  public function checkPermissionsHasAnyOf($requiredPermissions, User $user = null)
490  {
491  assert('is_int($requiredPermissions)');
492  if ($user == null)
493  {
494  $user = Yii::app()->user->userModel;
495  }
496  $effectivePermissions = $this->getEffectivePermissions($user);
497  if (($effectivePermissions & $requiredPermissions) == 0)
498  {
499  throw new AccessDeniedSecurityException($user, $requiredPermissions, $effectivePermissions);
500  }
501  }
502 
503  public static function getDefaultMetadata()
504  {
505  $metadata = parent::getDefaultMetadata();
506  $metadata[__CLASS__] = array(
507  'relations' => array(
508  'permissions' => array(static::HAS_MANY, 'Permission', static::OWNED),
509  ),
510  'indexes' => array(
511  'item_id' => array(
512  'members' => array('item_id'),
513  'unique' => false),
514  ),
515  );
516  return $metadata;
517  }
518 
519  public static function isTypeDeletable()
520  {
521  return false;
522  }
523 
527  public static function hasReadPermissionsOptimization()
528  {
529  return false;
530  }
531 
535  protected function afterSave()
536  {
537  parent::afterSave();
539  $this->permissionsChanged = false;
540  }
541 
545  protected function resolvePermitablesToUpdate()
546  {
547  $explicitReadWriteModelPermissions = ExplicitReadWriteModelPermissionsUtil::makeBySecurableItem($this);
548  $this->resolveRelativePermitablesToBeUpdated($explicitReadWriteModelPermissions);
549  if ($this->isPermitableUpdateRequired($explicitReadWriteModelPermissions))
550  {
551  $this->updatePermitables($explicitReadWriteModelPermissions);
552  }
553  }
554 
561  protected function updatePermitables(ExplicitReadWriteModelPermissions $explicitReadWriteModelPermissions)
562  {
563  $this->isSaving = false;
564  $this->resolvePermitablesToAttach($explicitReadWriteModelPermissions);
565  $this->resolvePermitablesToDetach($explicitReadWriteModelPermissions);
567  $this,
568  $explicitReadWriteModelPermissions);
569  if (!$permissionsUpdated || !$this->save(false))
570  {
571  throw new NotSupportedException('Unable to update permissions of model');
572  }
573  }
574 
580  protected function isPermitableUpdateRequired(ExplicitReadWriteModelPermissions $explicitReadWriteModelPermissions)
581  {
582  return (!(empty($this->_permitablesToAttachAfterSave) && empty($this->_permitablesToDetachAfterSave)));
583  }
584 
591  protected function resolveRelativePermitablesToBeUpdated(ExplicitReadWriteModelPermissions $explicitReadWriteModelPermissions)
592  {
593  // If same permitable exists in the attachment and detachment list, attachment takes precedence
594  // this has to be done before we calculate relative attachment list below else we would end
595  // up removing a permitable that was in attachment list and already existing too.
596  $this->_permitablesToDetachAfterSave = array_diff($this->_permitablesToDetachAfterSave,
597  $this->_permitablesToAttachAfterSave);
598 
599  // calculate new permitables to add relative to existing ones, do not re-add existing ones.
600  $existingPermitables = $explicitReadWriteModelPermissions->getReadWritePermitables();
601  $this->_permitablesToAttachAfterSave = array_diff($this->_permitablesToAttachAfterSave, $existingPermitables);
602  }
603 
608  protected function resolvePermitablesToAttach(ExplicitReadWriteModelPermissions $explicitReadWriteModelPermissions)
609  {
610  foreach ($this->_permitablesToAttachAfterSave as $permitable)
611  {
612  $explicitReadWriteModelPermissions->addReadWritePermitable($permitable);
613  }
614  // this is what prevents infinite loops of saves
615  $this->_permitablesToAttachAfterSave = array();
616  }
617 
622  protected function resolvePermitablesToDetach(ExplicitReadWriteModelPermissions $explicitReadWriteModelPermissions)
623  {
624  foreach ($this->_permitablesToDetachAfterSave as $permitable)
625  {
626  $explicitReadWriteModelPermissions->addReadWritePermitableToRemove($permitable);
627  }
628  // this is what prevents infinite loops of saves
629  $this->_permitablesToDetachAfterSave = array();
630  }
631 
639  public function addPermitableToAttachAfterSave(Permitable $permitable, $checkDetachBeforeAddition = false)
640  {
641  if (!$checkDetachBeforeAddition || !$this->removePermitableFromPermitablesToDetachAfterSave($permitable))
642  {
643  $this->_permitablesToAttachAfterSave[] = $permitable;
644  }
645  }
646 
653  {
654  $key = array_search($permitable, $this->_permitablesToAttachAfterSave);
655  if ($key !== false)
656  {
657  unset($this->_permitablesToAttachAfterSave[$key]);
658  return true;
659  }
660  return false;
661  }
662 
670  public function addPermitableToDetachAfterSave(Permitable $permitable, $checkAttachBeforeAddition = false)
671  {
672  if (!$checkAttachBeforeAddition || !$this->removePermitableFromPermitablesToAttachAfterSave($permitable))
673  {
674  $this->_permitablesToDetachAfterSave[] = $permitable;
675  }
676  }
677 
684  {
685  $key = array_search($permitable, $this->_permitablesToDetachAfterSave);
686  if ($key !== false)
687  {
688  unset($this->_permitablesToDetachAfterSave[$key]);
689  return true;
690  }
691  return false;
692  }
693 
694  public function arePermissionsChanged()
695  {
696  return $this->permissionsChanged;
697  }
698  }
699 ?>
static makeBySecurableItem(SecurableItem $securableItem)
removePermissions(Permitable $permitable, $permissions=Permission::ALL, $type=Permission::ALLOW_DENY)
static resolveExplicitReadWriteModelPermissions(SecurableItem $securableItem, ExplicitReadWriteModelPermissions $explicitReadWriteModelPermissions, $validate=false)
Definition: User.php:37
removePermitableFromPermitablesToAttachAfterSave(Permitable $permitable)
static forgetSecurableItemForRead(SecurableItem $securableItem)
addPermitableToDetachAfterSave(Permitable $permitable, $checkAttachBeforeAddition=false)
static supportsAndAllowsDatabaseCaching()
Definition: ZurmoCache.php:189
resolvePermitablesToAttach(ExplicitReadWriteModelPermissions $explicitReadWriteModelPermissions)
static hasReadPermissionsOptimization()
isPermitableUpdateRequired(ExplicitReadWriteModelPermissions $explicitReadWriteModelPermissions)
resolvePermitablesToDetach(ExplicitReadWriteModelPermissions $explicitReadWriteModelPermissions)
static cacheCombinedPermissions(SecurableItem $securableItem, Permitable $permitable, $combinedPermissions)
resolveRelativePermitablesToBeUpdated(ExplicitReadWriteModelPermissions $explicitReadWriteModelPermissions)
static getByName($name)
Definition: Group.php:57
addPermissions(Permitable $permitable, $permissions, $type=Permission::ALLOW)
Definition: Item.php:37
updatePermitables(ExplicitReadWriteModelPermissions $explicitReadWriteModelPermissions)
removePermitableFromPermitablesToDetachAfterSave(Permitable $permitable)
processGetActualPermissionsAsNonOptimized()
addPermitableToAttachAfterSave(Permitable $permitable, $checkDetachBeforeAddition=false)
checkPermissionsHasAnyOf($requiredPermissions, User $user=null)
static getCombinedPermissions(SecurableItem $securableItem, Permitable $permitable)
getActualPermissions($permitable=null)
Generated on Tue Jul 14 2020 07:10:37
Account Suspended
Account Suspended
This Account has been suspended.
Contact your hosting provider for more information.