PageRenderTime 26ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/blog/core/model/modx/modaccessibleobject.class.php

https://bitbucket.org/orchdork10159/dnsman.ly
PHP | 286 lines | 194 code | 10 blank | 82 comment | 85 complexity | 409677e8069ab96a6a8fef54477ca7db MD5 | raw file
  1. <?php
  2. /**
  3. * @package modx
  4. */
  5. /**
  6. * Defines an interface to provide configurable access policies for principals.
  7. *
  8. * @property modX|xPDO $xpdo
  9. * @package modx
  10. */
  11. class modAccessibleObject extends xPDOObject {
  12. /**
  13. * A local cache of access policies for the instance.
  14. * @var array
  15. */
  16. protected $_policies = array();
  17. /**
  18. * Custom instance from row loader that respects policy checking
  19. *
  20. * @param xPDO|modX $xpdo A reference to the xPDO/modX object.
  21. * @param string $className The name of the class by which to grab the instance from
  22. * @param mixed $criteria A criteria to use when grabbing this instance
  23. * @param int $row The row to select
  24. * @return modAccessibleObject|null An instance of the object
  25. */
  26. public static function _loadInstance(& $xpdo, $className, $criteria, $row) {
  27. /** @var modAccessibleObject $instance */
  28. $instance = xPDOObject :: _loadInstance($xpdo, $className, $criteria, $row);
  29. if ($instance instanceof modAccessibleObject && !$instance->checkPolicy('load')) {
  30. if ($xpdo instanceof modX) {
  31. $userid = $xpdo->getLoginUserID();
  32. if (!$userid) $userid = '0';
  33. $xpdo->log(xPDO::LOG_LEVEL_INFO, "Principal {$userid} does not have permission to load object of class {$instance->_class} with primary key: " . (is_object($instance) && method_exists($instance,'getPrimaryKey') ? print_r($instance->getPrimaryKey(), true) : ''));
  34. }
  35. $instance = null;
  36. }
  37. return $instance;
  38. }
  39. /**
  40. * Custom instance loader for collections that respects policy checking.
  41. *
  42. * {@inheritdoc}
  43. */
  44. public static function _loadCollectionInstance(xPDO & $xpdo, array & $objCollection, $className, $criteria, $row, $fromCache, $cacheFlag=true) {
  45. $loaded = false;
  46. if ($obj= modAccessibleObject :: _loadInstance($xpdo, $className, $criteria, $row)) {
  47. if (($cacheKey= $obj->getPrimaryKey()) && !$obj->isLazy()) {
  48. if (is_array($cacheKey)) {
  49. $pkval= implode('-', $cacheKey);
  50. } else {
  51. $pkval= $cacheKey;
  52. }
  53. if ($xpdo->getOption(xPDO::OPT_CACHE_DB_COLLECTIONS, array(), 1) == 2 && $xpdo->_cacheEnabled && $cacheFlag) {
  54. if (!$fromCache) {
  55. $pkCriteria = $xpdo->newQuery($className, $cacheKey, $cacheFlag);
  56. $xpdo->toCache($pkCriteria, $obj, $cacheFlag);
  57. } else {
  58. $obj->_cacheFlag= true;
  59. }
  60. }
  61. $objCollection[$pkval]= $obj;
  62. $loaded = true;
  63. } else {
  64. $objCollection[]= $obj;
  65. $loaded = true;
  66. }
  67. }
  68. return $loaded;
  69. }
  70. /**
  71. * Custom instance loader that forces access policy checking.
  72. *
  73. * {@inheritdoc}
  74. */
  75. public static function load(xPDO & $xpdo, $className, $criteria, $cacheFlag= true) {
  76. $instance= null;
  77. $fromCache= false;
  78. if ($className= $xpdo->loadClass($className)) {
  79. if (!is_object($criteria)) {
  80. $criteria= $xpdo->getCriteria($className, $criteria, $cacheFlag);
  81. }
  82. if (is_object($criteria)) {
  83. $row= null;
  84. if ($xpdo->_cacheEnabled && $criteria->cacheFlag && $cacheFlag) {
  85. $row= $xpdo->fromCache($criteria, $className);
  86. }
  87. if ($row === null || !is_array($row)) {
  88. if ($rows= xPDOObject :: _loadRows($xpdo, $className, $criteria)) {
  89. $row= $rows->fetch(PDO::FETCH_ASSOC);
  90. $rows->closeCursor();
  91. }
  92. } else {
  93. $fromCache= true;
  94. }
  95. if (!is_array($row)) {
  96. if ($xpdo->getDebug() === true) $xpdo->log(xPDO::LOG_LEVEL_DEBUG, "Fetched empty result set from statement: " . print_r($criteria->sql, true) . " with bindings: " . print_r($criteria->bindings, true));
  97. } else {
  98. $instance= modAccessibleObject :: _loadInstance($xpdo, $className, $criteria, $row);
  99. if (is_object($instance)) {
  100. if (!$fromCache && $cacheFlag && $xpdo->_cacheEnabled) {
  101. $xpdo->toCache($criteria, $instance, $cacheFlag);
  102. if ($xpdo->getOption(xPDO::OPT_CACHE_DB_OBJECTS_BY_PK) && ($cacheKey= $instance->getPrimaryKey()) && !$instance->isLazy()) {
  103. $pkCriteria = $xpdo->newQuery($className, $cacheKey, $cacheFlag);
  104. $xpdo->toCache($pkCriteria, $instance, $cacheFlag);
  105. }
  106. }
  107. if ($xpdo->getDebug() === true) $xpdo->log(xPDO::LOG_LEVEL_DEBUG, "Loaded object instance: " . print_r($instance->toArray('', true), true));
  108. }
  109. }
  110. } else {
  111. $xpdo->log(xPDO::LOG_LEVEL_ERROR, 'No valid statement could be found in or generated from the given criteria.');
  112. }
  113. } else {
  114. $xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Invalid class specified: ' . $className);
  115. }
  116. return $instance;
  117. }
  118. /**
  119. * Custom collection loader that forces access policy checking.
  120. *
  121. * {@inheritdoc}
  122. */
  123. public static function loadCollection(xPDO & $xpdo, $className, $criteria= null, $cacheFlag= true) {
  124. $objCollection= array ();
  125. $fromCache = false;
  126. if (!$className= $xpdo->loadClass($className)) return $objCollection;
  127. $rows= false;
  128. $fromCache= false;
  129. $collectionCaching = (integer) $xpdo->getOption(xPDO::OPT_CACHE_DB_COLLECTIONS, array(), 1);
  130. if (!is_object($criteria)) {
  131. $criteria= $xpdo->getCriteria($className, $criteria, $cacheFlag);
  132. }
  133. if ($collectionCaching > 0 && $xpdo->_cacheEnabled && $cacheFlag) {
  134. $rows= $xpdo->fromCache($criteria);
  135. $fromCache = (is_array($rows) && !empty($rows));
  136. }
  137. if (!$fromCache && is_object($criteria)) {
  138. $rows= xPDOObject :: _loadRows($xpdo, $className, $criteria);
  139. }
  140. if (is_array ($rows)) {
  141. foreach ($rows as $row) {
  142. modAccessibleObject :: _loadCollectionInstance($xpdo, $objCollection, $className, $criteria, $row, $fromCache, $cacheFlag);
  143. }
  144. } elseif (is_object($rows)) {
  145. $cacheRows = array();
  146. while ($row = $rows->fetch(PDO::FETCH_ASSOC)) {
  147. modAccessibleObject :: _loadCollectionInstance($xpdo, $objCollection, $className, $criteria, $row, $fromCache, $cacheFlag);
  148. if ($collectionCaching > 0 && $xpdo->_cacheEnabled && $cacheFlag && !$fromCache) $cacheRows[] = $row;
  149. }
  150. if ($collectionCaching > 0 && $xpdo->_cacheEnabled && $cacheFlag && !$fromCache) $rows =& $cacheRows;
  151. }
  152. if (!$fromCache && $xpdo->_cacheEnabled && $collectionCaching > 0 && $cacheFlag && !empty($rows)) {
  153. $xpdo->toCache($criteria, $rows, $cacheFlag);
  154. }
  155. return $objCollection;
  156. }
  157. /**
  158. * Custom save that respects access policies.
  159. *
  160. * {@inheritdoc}
  161. */
  162. public function save($cacheFlag = null) {
  163. $saved = false;
  164. if (!$this->checkPolicy('save')) {
  165. $this->xpdo->error->failure($this->xpdo->lexicon('permission_denied'));
  166. }
  167. $saved = parent :: save($cacheFlag);
  168. return $saved;
  169. }
  170. /**
  171. * Custom remove that respects access policies.
  172. *
  173. * {@inheritdoc}
  174. */
  175. public function remove(array $ancestors= array ()) {
  176. $removed = false;
  177. if (!$this->checkPolicy('remove')) {
  178. $this->xpdo->error->failure($this->xpdo->lexicon('permission_denied'));
  179. }
  180. $removed = parent :: remove($ancestors);
  181. return $removed;
  182. }
  183. /**
  184. * Determine if the current user attributes satisfy the object policy.
  185. *
  186. * @param array $criteria An associative array providing a key and value to
  187. * search for within the matched policy attributes between policy and
  188. * principal.
  189. * @param string|array $targets A target modAccess class name or an array of
  190. * class names to limit the check. In most cases, this does not need to be
  191. * set; derivatives should typically determine what targets to include in
  192. * the findPolicy() implementation.
  193. * @return boolean Returns true if the policy is satisfied or no policy
  194. * exists.
  195. */
  196. public function checkPolicy($criteria, $targets = null) {
  197. if ($criteria && $this->xpdo instanceof modX && $this->xpdo->getSessionState() == modX::SESSION_STATE_INITIALIZED) {
  198. if ($this->xpdo->user->get('sudo')) return true;
  199. if (!is_array($criteria) && is_scalar($criteria)) {
  200. $criteria = array("{$criteria}" => true);
  201. }
  202. $policy = $this->findPolicy();
  203. if (!empty($policy)) {
  204. $principal = $this->xpdo->user->getAttributes($targets);
  205. if (!empty($principal)) {
  206. foreach ($policy as $policyAccess => $access) {
  207. foreach ($access as $targetId => $targetPolicy) {
  208. foreach ($targetPolicy as $policyIndex => $applicablePolicy) {
  209. if ($this->xpdo->getDebug() === true)
  210. $this->xpdo->log(xPDO::LOG_LEVEL_DEBUG, 'target pk='. $this->getPrimaryKey() .'; evaluating policy: ' . print_r($applicablePolicy, 1) . ' against principal for user id=' . $this->xpdo->getLoginUserID() .': ' . print_r($principal[$policyAccess], 1));
  211. $principalPolicyData = array();
  212. $principalAuthority = 9999;
  213. if (isset($principal[$policyAccess][$targetId]) && is_array($principal[$policyAccess][$targetId])) {
  214. foreach ($principal[$policyAccess][$targetId] as $acl) {
  215. $principalAuthority = intval($acl['authority']);
  216. $principalPolicyData = $acl['policy'];
  217. $principalId = $acl['principal'];
  218. if ($applicablePolicy['principal'] == $principalId) {
  219. if ($principalAuthority <= $applicablePolicy['authority']) {
  220. if (!$applicablePolicy['policy']) {
  221. return true;
  222. }
  223. if (empty($principalPolicyData)) $principalPolicyData = array();
  224. $matches = array_intersect_assoc($principalPolicyData, $applicablePolicy['policy']);
  225. if ($matches) {
  226. if ($this->xpdo->getDebug() === true)
  227. $this->xpdo->log(modX::LOG_LEVEL_DEBUG, 'Evaluating policy matches: ' . print_r($matches, 1));
  228. $matched = array_diff_assoc($criteria, $matches);
  229. if (empty($matched)) {
  230. return true;
  231. }
  232. }
  233. }
  234. }
  235. }
  236. }
  237. }
  238. }
  239. }
  240. }
  241. return false;
  242. }
  243. }
  244. return true;
  245. }
  246. /**
  247. * Find access policies applicable to this object in a specific context.
  248. *
  249. * @access protected
  250. * @param string $context A key identifying a specific context to use when
  251. * searching for the applicable policies. If not provided, the current
  252. * context is used.
  253. * @return array An array of access policies for this object; an empty
  254. * array is returned if no policies are assigned to the object.
  255. */
  256. public function findPolicy($context = '') {
  257. return array();
  258. }
  259. /**
  260. * Return the currently loaded array of policies.
  261. *
  262. * @return array
  263. */
  264. public function getPolicies() {
  265. return $this->_policies;
  266. }
  267. /**
  268. * Set the current object's policies.
  269. *
  270. * @param array $policies
  271. * @return void
  272. */
  273. public function setPolicies(array $policies = array()) {
  274. $this->_policies = $policies;
  275. }
  276. }