AbstractPermissionManager.php
1 <?php
2 /**
3  * wCMF - wemove Content Management Framework
4  * Copyright (C) 2005-2015 wemove digital solutions GmbH
5  *
6  * Licensed under the terms of the MIT License.
7  *
8  * See the LICENSE file distributed with this work for
9  * additional information.
10  */
11 namespace wcmf\lib\security\impl;
12 
23 
24 /**
25  * AbstractPermissionManager is the base class for concrete PermissionManager
26  * implementations.
27  *
28  * @author ingo herwig <ingo@wemove.com>
29  */
31 
32  const RESOURCE_TYPE_ENTITY_TYPE = 'entity.type';
33  const RESOURCE_TYPE_ENTITY_TYPE_PROPERTY = 'entity.type.property';
34  const RESOURCE_TYPE_ENTITY_INSTANCE = 'entity.instance';
35  const RESOURCE_TYPE_ENTITY_INSTANCE_PROPERTY = 'entity.instance.property';
36  const RESOURCE_TYPE_OTHER = 'other';
37 
38  private $_tempPermissions = array();
39 
40  private static $_logger = null;
41 
42  protected $_persistenceFacade = null;
43  protected $_session = null;
44 
45  /**
46  * Constructor
47  * @param $persistenceFacade
48  * @param $session
49  */
50  public function __construct(PersistenceFacade $persistenceFacade,
51  Session $session) {
52  $this->_persistenceFacade = $persistenceFacade;
53  $this->_session = $session;
54  if (self::$_logger == null) {
55  self::$_logger = LogManager::getLogger(__CLASS__);
56  }
57  }
58 
59  /**
60  * @see PermissionManager::authorize()
61  */
62  public function authorize($resource, $context, $action, User $user=null) {
63  if ($user == null) {
64  $user = $this->_session->getAuthUser();
65  }
66  if (self::$_logger->isDebugEnabled()) {
67  self::$_logger->debug("Checking authorization for: '$resource?$context?$action' and user '".$user->getLogin()."'");
68  }
69 
70  // normalize resource to string
71  $resourceStr = ($resource instanceof ObjectId) ? $resource->__toString() : $resource;
72 
73  // determine the resource type and set entity type, oid and property if applying
74  $extensionRemoved = preg_replace('/\.[^\.]*?$/', '', $resourceStr);
75  $resourceType = null;
76  $oid = null;
77  $type = null;
78  $oidProperty = null;
79  $typeProperty = null;
80  if (($oidObj = ObjectId::parse($resourceStr)) !== null) {
81  $resourceType = self::RESOURCE_TYPE_ENTITY_INSTANCE;
82  $oid = $resourceStr;
83  $type = $oidObj->getType();
84  }
85  elseif (($oidObj = ObjectId::parse($extensionRemoved)) !== null) {
86  $resourceType = self::RESOURCE_TYPE_ENTITY_INSTANCE_PROPERTY;
87  $oid = $extensionRemoved;
88  $type = $oidObj->getType();
89  $oidProperty = $resourceStr;
90  $typeProperty = $type.substr($resourceStr, strlen($extensionRemoved));
91  }
92  elseif ($this->_persistenceFacade->isKnownType($resourceStr)) {
93  $resourceType = self::RESOURCE_TYPE_ENTITY_TYPE;
94  $type = $resourceStr;
95  }
96  elseif ($this->_persistenceFacade->isKnownType($extensionRemoved)) {
97  $resourceType = self::RESOURCE_TYPE_ENTITY_TYPE_PROPERTY;
98  $type = $extensionRemoved;
99  $typeProperty = $resourceStr;
100  }
101  else {
102  // defaults to other
103  $resourceType = self::RESOURCE_TYPE_OTHER;
104  }
105  if (self::$_logger->isDebugEnabled()) {
106  self::$_logger->debug("Resource type: ".$resourceType);
107  }
108 
109  // proceed by authorizing type depending resource
110  // always start checking from most specific
111  switch ($resourceType) {
112  case (self::RESOURCE_TYPE_ENTITY_INSTANCE_PROPERTY):
113  $authorized = $this->authorizeAction($oidProperty, $context, $action, $user);
114  if ($authorized === null) {
115  $authorized = $this->authorizeAction($typeProperty, $context, $action, $user);
116  if ($authorized === null) {
117  $authorized = $this->authorizeAction($oid, $context, $action, $user);
118  if ($authorized === null) {
119  $authorized = $this->authorizeAction($type, $context, $action, $user);
120  }
121  }
122  }
123  break;
124 
125  case (self::RESOURCE_TYPE_ENTITY_INSTANCE):
126  $authorized = $this->authorizeAction($oid, $context, $action, $user);
127  if ($authorized === null) {
128  $authorized = $this->authorizeAction($type, $context, $action, $user);
129  }
130  break;
131 
132  case (self::RESOURCE_TYPE_ENTITY_TYPE_PROPERTY):
133  $authorized = $this->authorizeAction($typeProperty, $context, $action, $user);
134  if ($authorized === null) {
135  $authorized = $this->authorizeAction($type, $context, $action, $user);
136  }
137  break;
138 
139  case (self::RESOURCE_TYPE_ENTITY_TYPE_PROPERTY):
140  $authorized = $this->authorizeAction($type, $context, $action, $user);
141  break;
142 
143  default:
144  $authorized = $this->authorizeAction($resourceStr, $context, $action, $user);
145  break;
146  }
147 
148  // check parent entities in composite relations
149  if ($authorized === null && $resourceType == self::RESOURCE_TYPE_ENTITY_INSTANCE) {
150  if (self::$_logger->isDebugEnabled()) {
151  self::$_logger->debug("Check parent objects");
152  }
153  $mapper = $this->_persistenceFacade->getMapper($type);
154  $parentRelations = $mapper->getRelations('parent');
155  if (sizeof($parentRelations) > 0) {
156 
157  $this->addTempPermission($oidObj, $context, PersistenceAction::READ);
158  $object = $this->_persistenceFacade->load($oidObj);
159  $this->removeTempPermission($oidObj, $context, PersistenceAction::READ);
160 
161  if ($object != null) {
162  foreach ($parentRelations as $parentRelation) {
163  if ($parentRelation->getThisAggregationKind() == 'composite') {
164  $parentType = $parentRelation->getOtherType();
165 
166  $this->addTempPermission($parentType, $context, PersistenceAction::READ);
167  $parents = $object->getValue($parentRelation->getOtherRole());
168  $this->removeTempPermission($parentType, $context, PersistenceAction::READ);
169 
170  if ($parents != null) {
171  if (!$parentRelation->isMultiValued()) {
172  $parents = array($parents);
173  }
174  foreach ($parents as $parent) {
175  $authorized = $this->authorize($parent->getOID(), $context, $action);
176  if (!$authorized) {
177  break;
178  }
179  }
180  }
181  }
182  }
183  }
184  }
185  }
186 
187  if ($authorized === null) {
188  $authorized = $this->getDefaultPolicy($user);
189  }
190  if (self::$_logger->isDebugEnabled()) {
191  self::$_logger->debug("Result for $resource?$context?$action: ".(!$authorized ? "not " : "")."authorized");
192  }
193 
194  return $authorized;
195  }
196 
197  /**
198  * Authorize the given resource, context, action triple using the
199  * temporary permissions or the current user.
200  * @param $resource The resource to authorize (e.g. class name of the Controller or ObjectId instance).
201  * @param $context The context in which the action takes place.
202  * @param $action The action to process.
203  * @param $user User instance to use for authorization
204  * @param $returnNullIfNoPermissionExists Optional, default: true
205  * @return Boolean
206  */
207  protected function authorizeAction($resource, $context, $action, User $user, $returnNullIfNoPermissionExists=true) {
208  if (self::$_logger->isDebugEnabled()) {
209  self::$_logger->debug("Authorizing $resource?$context?$action");
210  }
211  $authorized = null;
212 
213  // check temporary permissions
214  if ($this->hasTempPermission($resource, $context, $action)) {
215  if (self::$_logger->isDebugEnabled()) {
216  self::$_logger->debug("Has temporary permission");
217  }
218  $authorized = true;
219  }
220  else {
221  // check other permissions
222  $permissions = $this->getPermissions($resource, $context, $action);
223  if (self::$_logger->isDebugEnabled()) {
224  self::$_logger->debug("Permissions: ".StringUtil::getDump($permissions));
225  }
226  if ($permissions != null) {
227  // matching permissions found, check user roles
228  $authorized = $this->matchRoles($permissions, $user);
229  }
230  elseif (!$returnNullIfNoPermissionExists) {
231  // no permission definied, check for user's default policy
232  $authorized = $this->getDefaultPolicy($user);
233  }
234  }
235  if (self::$_logger->isDebugEnabled()) {
236  self::$_logger->debug("Result for $resource?$context?$action: ".(is_bool($authorized) ? ((!$authorized ? "not " : "")."authorized") : "not defined"));
237  }
238  return $authorized;
239  }
240 
241  /**
242  * Get the default policy that is used if no permission is set up
243  * for a requested action.
244  * @return Boolean
245  */
246  protected function getDefaultPolicy(User $user) {
247  return ($user instanceof AnonymousUser) ? false : true;
248  }
249 
250  /**
251  * Parse a permissions string and return an associative array with the keys
252  * 'default', 'allow', 'deny', where 'allow', 'deny' are arrays itselves holding roles
253  * and 'default' is a boolean value derived from the wildcard policy (+* or -*).
254  * @param $val A role string (+*, +administrators, -guest, entries without '+' or '-'
255  * prefix default to allow rules).
256  * @return Associative array containing the permissions as an associative array with the keys
257  * 'default', 'allow', 'deny' or null, if val is empty
258  */
259  protected function deserializePermissions($val) {
260  if (strlen($val) == 0) {
261  return null;
262  }
263  $result = array(
264  'default' => null,
265  'allow' => array(),
266  'deny' => array(),
267  );
268 
269  $roleValues = explode(" ", $val);
270  foreach ($roleValues as $roleValue) {
271  $roleValue = trim($roleValue);
272  $matches = array();
273  preg_match('/^([+-]?)(.+)$/', $roleValue, $matches);
274  if (sizeof($matches) > 0) {
275  $prefix = $matches[1];
276  $role = $matches[2];
277  if ($role === '*') {
278  $result['default'] = $prefix == '-' ? false : true;
279  }
280  else {
281  if ($prefix === '-') {
282  $result['deny'][] = $role;
283  }
284  else {
285  // entries without '+' or '-' prefix default to allow rules
286  $result['allow'][] = $role;
287  }
288  }
289  }
290  }
291  // if no wildcard policy is defined, set default to false
292  if (!isset($result['default'])) {
293  $result['default'] = false;
294  }
295  return $result;
296  }
297 
298  /**
299  * Convert an associative permissions array with keys 'default', 'allow', 'deny'
300  * into a string.
301  * @param $permissions Associative array with keys 'default', 'allow', 'deny',
302  * where 'allow', 'deny' are arrays itselves holding roles and 'default' is a
303  * boolean value derived from the wildcard policy (+* or -*).
304  * @return A role string (+*, +administrators, -guest, entries without '+' or '-'
305  * prefix default to allow rules).
306  */
307  protected function serializePermissions($permissions) {
308  $result = $permissions['default'] === true ? PermissionManager::PERMISSION_MODIFIER_ALLOW.'* ' :
310  if (isset($permissions['allow'])) {
311  foreach ($permissions['allow'] as $role) {
313  }
314  }
315  if (isset($permissions['deny'])) {
316  foreach ($permissions['deny'] as $role) {
317  $result .= PermissionManager::PERMISSION_MODIFIER_DENY.$role.' ';
318  }
319  }
320  return trim($result);
321  }
322 
323  /**
324  * Matches the roles of the user and the roles in the given permissions
325  * @param $permissions An array containing permissions as an associative array
326  * with the keys 'default', 'allow', 'deny', where 'allow', 'deny' are arrays
327  * itselves holding roles and 'default' is a boolean value derived from the
328  * wildcard policy (+* or -*). 'allow' overwrites 'deny' overwrites 'default'
329  * @param $user AuthUser instance
330  * @return Boolean whether the user has access right according to the permissions.
331  */
332  protected function matchRoles($permissions, User $user) {
333  if (self::$_logger->isDebugEnabled()) {
334  self::$_logger->debug("Matching roles for ".$user->getLogin());
335  }
336  if (isset($permissions['allow'])) {
337  foreach ($permissions['allow'] as $value) {
338  if ($user->hasRole($value)) {
339  if (self::$_logger->isDebugEnabled()) {
340  self::$_logger->debug("Allowed because of role ".$value);
341  }
342  return true;
343  }
344  }
345  }
346  if (isset($permissions['deny'])) {
347  foreach ($permissions['deny'] as $value) {
348  if ($user->hasRole($value)) {
349  if (self::$_logger->isDebugEnabled()) {
350  self::$_logger->debug("Denied because of role ".$value);
351  }
352  return false;
353  }
354  }
355  }
356  if (self::$_logger->isDebugEnabled()) {
357  self::$_logger->debug("Check default ".$permissions['default']);
358  }
359  return isset($permissions['default']) ? $permissions['default'] : false;
360  }
361 
362  /**
363  * @see PermissionManager::addTempPermission()
364  */
365  public function addTempPermission($resource, $context, $action) {
366  $actionKey = ActionKey::createKey($resource, $context, $action);
367  if (self::$_logger->isDebugEnabled()) {
368  self::$_logger->debug("Adding temporary permission for '$actionKey'");
369  }
370  $this->_tempPermissions[$actionKey] = true;
371  }
372 
373  /**
374  * @see PermissionManager::removeTempPermission()
375  */
376  public function removeTempPermission($resource, $context, $action) {
377  $actionKey = ActionKey::createKey($resource, $context, $action);
378  if (self::$_logger->isDebugEnabled()) {
379  self::$_logger->debug("Removing temporary permission for '$actionKey'");
380  }
381  unset($this->_tempPermissions[$actionKey]);
382  }
383 
384  /**
385  * @see PermissionManager::hasTempPermission()
386  */
387  public function hasTempPermission($resource, $context, $action) {
388  $actionKey = ActionKey::createKey($resource, $context, $action);
389  return isset($this->_tempPermissions[$actionKey]);
390  }
391 
392  /**
393  * @see PermissionManager::clearTempPermissions()
394  */
395  public function clearTempPermissions() {
396  $this->_tempPermissions = array();
397  }
398 }
399 ?>
serializePermissions($permissions)
Convert an associative permissions array with keys 'default', 'allow', 'deny' into a string...
getDefaultPolicy(User $user)
Get the default policy that is used if no permission is set up for a requested action.
authorize($resource, $context, $action, User $user=null)
User is the interface for users.
Definition: User.php:18
AbstractPermissionManager is the base class for concrete PermissionManager implementations.
ObjectId is the unique identifier of an object.
Definition: ObjectId.php:27
static getLogger($name)
Get the logger with the given name.
Definition: LogManager.php:35
deserializePermissions($val)
Parse a permissions string and return an associative array with the keys 'default', 'allow', 'deny', where 'allow', 'deny' are arrays itselves holding roles and 'default' is a boolean value derived from the wildcard policy (+* or -*).
Session is the interface for session implementations and defines access to session variables...
Definition: Session.php:21
static parse($oid)
Parse a serialized object id string into an ObjectId instance.
Definition: ObjectId.php:144
authorizeAction($resource, $context, $action, User $user, $returnNullIfNoPermissionExists=true)
Authorize the given resource, context, action triple using the temporary permissions or the current u...
__construct(PersistenceFacade $persistenceFacade, Session $session)
Constructor.
static createKey($resource, $context, $action)
Create an action key from the given values.
Definition: ActionKey.php:33
hasRole($roleName)
Check for a certain role in the user roles.
PersistenceFacade defines the interface for PersistenceFacade implementations.
matchRoles($permissions, User $user)
Matches the roles of the user and the roles in the given permissions.
getLogin()
Get the login of the user.
static getDump($var)
Get the dump of a variable as string.
Definition: StringUtil.php:25