/core/model/modx/modaccessibleobject.class.php
PHP | 286 lines | 194 code | 10 blank | 82 comment | 85 complexity | 409677e8069ab96a6a8fef54477ca7db MD5 | raw file
- <?php
- /**
- * @package modx
- */
- /**
- * Defines an interface to provide configurable access policies for principals.
- *
- * @property modX|xPDO $xpdo
- * @package modx
- */
- class modAccessibleObject extends xPDOObject {
- /**
- * A local cache of access policies for the instance.
- * @var array
- */
- protected $_policies = array();
- /**
- * Custom instance from row loader that respects policy checking
- *
- * @param xPDO|modX $xpdo A reference to the xPDO/modX object.
- * @param string $className The name of the class by which to grab the instance from
- * @param mixed $criteria A criteria to use when grabbing this instance
- * @param int $row The row to select
- * @return modAccessibleObject|null An instance of the object
- */
- public static function _loadInstance(& $xpdo, $className, $criteria, $row) {
- /** @var modAccessibleObject $instance */
- $instance = xPDOObject :: _loadInstance($xpdo, $className, $criteria, $row);
- if ($instance instanceof modAccessibleObject && !$instance->checkPolicy('load')) {
- if ($xpdo instanceof modX) {
- $userid = $xpdo->getLoginUserID();
- if (!$userid) $userid = '0';
- $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) : ''));
- }
- $instance = null;
- }
- return $instance;
- }
- /**
- * Custom instance loader for collections that respects policy checking.
- *
- * {@inheritdoc}
- */
- public static function _loadCollectionInstance(xPDO & $xpdo, array & $objCollection, $className, $criteria, $row, $fromCache, $cacheFlag=true) {
- $loaded = false;
- if ($obj= modAccessibleObject :: _loadInstance($xpdo, $className, $criteria, $row)) {
- if (($cacheKey= $obj->getPrimaryKey()) && !$obj->isLazy()) {
- if (is_array($cacheKey)) {
- $pkval= implode('-', $cacheKey);
- } else {
- $pkval= $cacheKey;
- }
- if ($xpdo->getOption(xPDO::OPT_CACHE_DB_COLLECTIONS, array(), 1) == 2 && $xpdo->_cacheEnabled && $cacheFlag) {
- if (!$fromCache) {
- $pkCriteria = $xpdo->newQuery($className, $cacheKey, $cacheFlag);
- $xpdo->toCache($pkCriteria, $obj, $cacheFlag);
- } else {
- $obj->_cacheFlag= true;
- }
- }
- $objCollection[$pkval]= $obj;
- $loaded = true;
- } else {
- $objCollection[]= $obj;
- $loaded = true;
- }
- }
- return $loaded;
- }
- /**
- * Custom instance loader that forces access policy checking.
- *
- * {@inheritdoc}
- */
- public static function load(xPDO & $xpdo, $className, $criteria, $cacheFlag= true) {
- $instance= null;
- $fromCache= false;
- if ($className= $xpdo->loadClass($className)) {
- if (!is_object($criteria)) {
- $criteria= $xpdo->getCriteria($className, $criteria, $cacheFlag);
- }
- if (is_object($criteria)) {
- $row= null;
- if ($xpdo->_cacheEnabled && $criteria->cacheFlag && $cacheFlag) {
- $row= $xpdo->fromCache($criteria, $className);
- }
- if ($row === null || !is_array($row)) {
- if ($rows= xPDOObject :: _loadRows($xpdo, $className, $criteria)) {
- $row= $rows->fetch(PDO::FETCH_ASSOC);
- $rows->closeCursor();
- }
- } else {
- $fromCache= true;
- }
- if (!is_array($row)) {
- 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));
- } else {
- $instance= modAccessibleObject :: _loadInstance($xpdo, $className, $criteria, $row);
- if (is_object($instance)) {
- if (!$fromCache && $cacheFlag && $xpdo->_cacheEnabled) {
- $xpdo->toCache($criteria, $instance, $cacheFlag);
- if ($xpdo->getOption(xPDO::OPT_CACHE_DB_OBJECTS_BY_PK) && ($cacheKey= $instance->getPrimaryKey()) && !$instance->isLazy()) {
- $pkCriteria = $xpdo->newQuery($className, $cacheKey, $cacheFlag);
- $xpdo->toCache($pkCriteria, $instance, $cacheFlag);
- }
- }
- if ($xpdo->getDebug() === true) $xpdo->log(xPDO::LOG_LEVEL_DEBUG, "Loaded object instance: " . print_r($instance->toArray('', true), true));
- }
- }
- } else {
- $xpdo->log(xPDO::LOG_LEVEL_ERROR, 'No valid statement could be found in or generated from the given criteria.');
- }
- } else {
- $xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Invalid class specified: ' . $className);
- }
- return $instance;
- }
- /**
- * Custom collection loader that forces access policy checking.
- *
- * {@inheritdoc}
- */
- public static function loadCollection(xPDO & $xpdo, $className, $criteria= null, $cacheFlag= true) {
- $objCollection= array ();
- $fromCache = false;
- if (!$className= $xpdo->loadClass($className)) return $objCollection;
- $rows= false;
- $fromCache= false;
- $collectionCaching = (integer) $xpdo->getOption(xPDO::OPT_CACHE_DB_COLLECTIONS, array(), 1);
- if (!is_object($criteria)) {
- $criteria= $xpdo->getCriteria($className, $criteria, $cacheFlag);
- }
- if ($collectionCaching > 0 && $xpdo->_cacheEnabled && $cacheFlag) {
- $rows= $xpdo->fromCache($criteria);
- $fromCache = (is_array($rows) && !empty($rows));
- }
- if (!$fromCache && is_object($criteria)) {
- $rows= xPDOObject :: _loadRows($xpdo, $className, $criteria);
- }
- if (is_array ($rows)) {
- foreach ($rows as $row) {
- modAccessibleObject :: _loadCollectionInstance($xpdo, $objCollection, $className, $criteria, $row, $fromCache, $cacheFlag);
- }
- } elseif (is_object($rows)) {
- $cacheRows = array();
- while ($row = $rows->fetch(PDO::FETCH_ASSOC)) {
- modAccessibleObject :: _loadCollectionInstance($xpdo, $objCollection, $className, $criteria, $row, $fromCache, $cacheFlag);
- if ($collectionCaching > 0 && $xpdo->_cacheEnabled && $cacheFlag && !$fromCache) $cacheRows[] = $row;
- }
- if ($collectionCaching > 0 && $xpdo->_cacheEnabled && $cacheFlag && !$fromCache) $rows =& $cacheRows;
- }
- if (!$fromCache && $xpdo->_cacheEnabled && $collectionCaching > 0 && $cacheFlag && !empty($rows)) {
- $xpdo->toCache($criteria, $rows, $cacheFlag);
- }
- return $objCollection;
- }
- /**
- * Custom save that respects access policies.
- *
- * {@inheritdoc}
- */
- public function save($cacheFlag = null) {
- $saved = false;
- if (!$this->checkPolicy('save')) {
- $this->xpdo->error->failure($this->xpdo->lexicon('permission_denied'));
- }
- $saved = parent :: save($cacheFlag);
- return $saved;
- }
- /**
- * Custom remove that respects access policies.
- *
- * {@inheritdoc}
- */
- public function remove(array $ancestors= array ()) {
- $removed = false;
- if (!$this->checkPolicy('remove')) {
- $this->xpdo->error->failure($this->xpdo->lexicon('permission_denied'));
- }
- $removed = parent :: remove($ancestors);
- return $removed;
- }
- /**
- * Determine if the current user attributes satisfy the object policy.
- *
- * @param array $criteria An associative array providing a key and value to
- * search for within the matched policy attributes between policy and
- * principal.
- * @param string|array $targets A target modAccess class name or an array of
- * class names to limit the check. In most cases, this does not need to be
- * set; derivatives should typically determine what targets to include in
- * the findPolicy() implementation.
- * @return boolean Returns true if the policy is satisfied or no policy
- * exists.
- */
- public function checkPolicy($criteria, $targets = null) {
- if ($criteria && $this->xpdo instanceof modX && $this->xpdo->getSessionState() == modX::SESSION_STATE_INITIALIZED) {
- if ($this->xpdo->user->get('sudo')) return true;
- if (!is_array($criteria) && is_scalar($criteria)) {
- $criteria = array("{$criteria}" => true);
- }
- $policy = $this->findPolicy();
- if (!empty($policy)) {
- $principal = $this->xpdo->user->getAttributes($targets);
- if (!empty($principal)) {
- foreach ($policy as $policyAccess => $access) {
- foreach ($access as $targetId => $targetPolicy) {
- foreach ($targetPolicy as $policyIndex => $applicablePolicy) {
- if ($this->xpdo->getDebug() === true)
- $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));
- $principalPolicyData = array();
- $principalAuthority = 9999;
- if (isset($principal[$policyAccess][$targetId]) && is_array($principal[$policyAccess][$targetId])) {
- foreach ($principal[$policyAccess][$targetId] as $acl) {
- $principalAuthority = intval($acl['authority']);
- $principalPolicyData = $acl['policy'];
- $principalId = $acl['principal'];
- if ($applicablePolicy['principal'] == $principalId) {
- if ($principalAuthority <= $applicablePolicy['authority']) {
- if (!$applicablePolicy['policy']) {
- return true;
- }
- if (empty($principalPolicyData)) $principalPolicyData = array();
- $matches = array_intersect_assoc($principalPolicyData, $applicablePolicy['policy']);
- if ($matches) {
- if ($this->xpdo->getDebug() === true)
- $this->xpdo->log(modX::LOG_LEVEL_DEBUG, 'Evaluating policy matches: ' . print_r($matches, 1));
- $matched = array_diff_assoc($criteria, $matches);
- if (empty($matched)) {
- return true;
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- return false;
- }
- }
- return true;
- }
- /**
- * Find access policies applicable to this object in a specific context.
- *
- * @access protected
- * @param string $context A key identifying a specific context to use when
- * searching for the applicable policies. If not provided, the current
- * context is used.
- * @return array An array of access policies for this object; an empty
- * array is returned if no policies are assigned to the object.
- */
- public function findPolicy($context = '') {
- return array();
- }
- /**
- * Return the currently loaded array of policies.
- *
- * @return array
- */
- public function getPolicies() {
- return $this->_policies;
- }
- /**
- * Set the current object's policies.
- *
- * @param array $policies
- * @return void
- */
- public function setPolicies(array $policies = array()) {
- $this->_policies = $policies;
- }
- }