/ */ class P4Cms_Acl extends Zend_Acl { const RECORD_STORAGE_FIELD = 'acl'; protected static $_activeAcl; protected $_record; protected $_tempResources = array(); /** * Provide magic sleep to selectively serialize properties. * This is needed for caching and saving the acl. Note that * save is even more selective about what gets serialized. * * @return array list of properties to serialize */ public function __sleep() { // remove temporary resources foreach ($this->_tempResources as $resource) { $this->remove($resource); } $this->_tempResources = array(); return array( '_roleRegistry', '_resources', '_record', '_rules' ); } /** * Overrides parent method to add resource temporarily if it doesn't exist, * but has the form 'parentResource/id' where parentResource is an existing * resource. Temporary resources are not serialized. * * @param Zend_Acl_Resource_Interface|string $resource resource to return identified * resource for. * @throws Zend_Acl_Exception * @return Zend_Acl_Resource_Interface identified resource. */ public function get($resource) { try { return parent::get($resource); } catch (Zend_Acl_Exception $e) { $resource = is_string($resource) ? $resource : $resource->getResourceId(); $resourceParts = explode('/', $resource); if (count($resourceParts) > 1 && $this->has($resourceParts[0])) { $resource = new P4Cms_Acl_Resource($resource); $this->addResource($resource, $resourceParts[0]); $this->_tempResources[] = $resource; return $resource; } else { // resource not found throw $e; } } } /** * Fetch the active acl. Guaranteed to return an acl instance or * throw an exception if no acl instance has been set as active. * * @return P4Cms_Acl the currently active acl. * @throws P4Cms_Acl_Exception if there is no currently active acl. */ public static function fetchActive() { if (!static::$_activeAcl || !static::$_activeAcl instanceof P4Cms_Acl) { throw new P4Cms_Acl_Exception("There is no active ACL."); } return static::$_activeAcl; } /** * Determine if there is an active acl. * * @return boolean true if there is an active acl. */ public static function hasActive() { try { static::fetchActive(); return true; } catch (P4Cms_Acl_Exception $e) { return false; } } /** * Set the statically accessible ACL instance. * * @param P4Cms_Acl|null $acl the acl instance to make active or null to clear. */ public static function setActive(P4Cms_Acl $acl = null) { static::$_activeAcl = $acl; } /** * Overrides isAllowed to return false for non-super roles * accessing privileges that require super-user access. * * @param Zend_Acl_Role_Interface|string $role the role to check for access * @param Zend_Acl_Resource_Interface|string $resource the resource to check access to * @param string $privilege the privilege in question * @return boolean */ public function isAllowed($role = null, $resource = null, $privilege = null) { $role = is_string($role) ? $this->getRole($role) : $role; $resource = is_string($resource) ? $this->get($resource) : $resource; // non-super roles are not allowed super privileges. if ($privilege && $resource->hasPrivilege($privilege)) { $needsSuper = $resource->getPrivilege($privilege)->getOption('needsSuper'); if ($needsSuper && !P4Cms_Acl_Role::isSuper($role->getRoleId())) { return false; } } return parent::isAllowed($role, $resource, $privilege); } /** * Returns list of all privileges for which the role has access to the resource. * * @param Zend_Acl_Role_Interface|string $role the role to check for * @param Zend_Acl_Resource_Interface|string $resource the resource to check for * @return array list of privileges for which the * role has access to the resource */ public function getAllowedPrivileges($role, $resource) { if ($resource instanceof P4Cms_Acl_Resource) { $privileges = $resource->getPrivileges(); } else { $privileges = $this->getAllPrivileges()->toArray(true); } $allowed = array(); foreach ($privileges as $privilege) { $privilege = $privilege->getId(); if ($this->isAllowed($role, $resource, $privilege)) { $allowed[$privilege] = 1; } } return array_keys($allowed); } /** * Make this acl instance the active one. * * @return P4Cms_Acl provides fluent interface. */ public function makeActive() { static::setActive($this); return $this; } /** * Get all of the registered resource objects. * * @return array list of resource instances */ public function getResourceObjects() { return array_map( function($resource) { return $resource['instance']; }, $this->_resources ); } /** * Set all roles at once. * * To reduce the weight of serialized roles we normalize them to * Zend_Acl_Role objects. Our role objects compose Perforce group * objects and spec definitions, etc. and are considerably larger. * * @param mixed $roles the set of roles to use for this acl * can be a Zend_Acl_Role_Registry instance, * a iterator of role objects, or null to clear. * @return P4Cms_Acl provides fluent interface. */ public function setRoles($roles = null) { // extract roles from iterator. if ($roles instanceof Iterator) { $registry = new Zend_Acl_Role_Registry; foreach ($roles as $role) { if (!$role instanceof Zend_Acl_Role_Interface) { throw new InvalidArgumentException( "Cannot set roles. Encountered invalid role." ); } $registry->add(new Zend_Acl_Role($role->getRoleId())); } $roles = $registry; } if ($roles !== null && !$roles instanceof Zend_Acl_Role_Registry) { throw new InvalidArgumentException( "Cannot set roles. Roles must be a Zend_Acl_Role_Registry instance, " . "a iterator of role objects, or null." ); } // set the registry. $this->_roleRegistry = $roles; return $this; } /** * Fetch a persisted ACL instance from a given record id. * Reads the identified record from storage and unserializes * it into a new P4Cms_Acl instance. * * @param string $id the id of the record to read from. * @param P4Cms_Record_Adapter $adapter optional - storage adapter to use. * @return P4Cms_Acl previously stored acl instance. */ public static function fetch($id, P4Cms_Record_Adapter $adapter = null) { $record = P4Cms_Record::fetch($id, null, $adapter); $serialized = $record->getValue(static::RECORD_STORAGE_FIELD); // attempt to unserialize acl from storage. $acl = unserialize($serialized); if (!$acl instanceof P4Cms_Acl) { throw new P4Cms_Acl_Exception( "Cannot fetch ACL from $id record. Record did not unserialize to ACL." ); } // hang onto record for potential future save. $acl->setRecord($record); return $acl; } /** * Set the record to use for persistent storage of this acl. * * @param P4Cms_Record|null $record a record object to save acl to - null to clear. * @return P4Cms_Acl provides fluent interface. */ public function setRecord(P4Cms_Record $record = null) { $this->_record = $record; return $this; } /** * Get the record to use for persistent storage of this acl. * * @return P4Cms_Record the record to save acl to. * @throws P4Cms_Acl_Exception if no record object has been set. */ public function getRecord() { if (!$this->_record instanceof P4Cms_Record) { throw new P4Cms_Acl_Exception("Cannot get record. No record has been set."); } return $this->_record; } /** * Determine if this acl has an associated record for storage purposes. * * @return boolean true if there is an associated record. */ public function hasRecord() { try { $this->getRecord(); return true; } catch (P4Cms_Acl_Exception $e) { return false; } } /** * Get all privileges in all resources registered with the acl. * * @return array a flat list of all privileges in all resources. */ public function getAllPrivileges() { $privileges = new P4Cms_Model_Iterator; foreach ($this->getResourceObjects() as $resource) { if ($resource instanceof P4Cms_Acl_Resource) { foreach ($resource->getPrivileges() as $privilege) { $privileges[] = $privilege; } } } return $privileges; } /** * Save this ACL to the associated record. * * @return P4Cms_Acl provides fluent interface. * @throws P4Cms_Acl_Exception if no record has been associated with this acl. */ public function save() { // we don't want roles or the record to make it into storage $acl = clone $this; $acl->setRoles(null) ->setRecord(null); $this->getRecord() ->setValue(static::RECORD_STORAGE_FIELD, serialize($acl)) ->save(); return $this; } /** * Install default ACL resources and rules defined in packages. * Does not save the acl automatically, must call save() to persist. * * @return P4Cms_Acl provides fluent interface. */ public function installDefaults() { // clear the module/theme cache P4Cms_Module::clearCache(); P4Cms_Theme::clearCache(); // defaults are defined by modules. $modules = P4Cms_Module::fetchAllEnabled(); foreach ($modules as $module) { $this->installModuleDefaults($module); } return $this; } /** * Install default ACL resources and rules defined in a module. * Does not save the acl automatically, must call save() to persist. * * @param P4Cms_Module $module the module whose ACL resources and rules need to be installed * @return P4Cms_Acl provides fluent interface. */ public function installModuleDefaults(P4Cms_Module $module) { // extract resources from package info. $info = $module->getPackageInfo(); $resources = isset($info['acl']) && is_array($info['acl']) ? $info['acl'] : array(); // register resources with acl. foreach ($resources as $resourceId => $resourceInfo) { // add resource if it doesn't exist. if (!$this->has($resourceId)) { $this->add(new P4Cms_Acl_Resource($resourceId)); } $resource = $this->get($resourceId); // set resource label if one is specified. if (isset($resourceInfo['label'])) { $resource->setLabel($resourceInfo['label']); } // add any new privileges to resource. $privileges = isset($resourceInfo['privileges']) && is_array($resourceInfo['privileges']) ? $resourceInfo['privileges'] : array(); foreach ($privileges as $key => $value) { // try to normalize privilege entry to object. try { $privilege = $resource->normalizePrivilege($value, $key); } catch (InvalidArgumentException $e) { P4Cms_Log::logException("Failed to install default privilege.", $e); continue; } // skip if privilege exists. if ($resource->hasPrivilege($privilege->getId())) { continue; } // add the privilege. $resource->addPrivilege($privilege); // use a proxy for assertions to allow for assert // classes that might not exist at all times. $assert = $privilege->getOption('assertion'); if ($assert) { $assert = new P4Cms_Acl_Assert_Proxy($assert); } // set default allow rules. // determine which roles to allow access for. if ($privilege->getOption('allowAll')) { $this->allow(null, $resource, $privilege->getId(), $assert); } else { $roles = $privilege->getDefaultAllowed(); $roles = array_filter($roles, array($this, 'hasRole')); if ($roles) { $this->allow($roles, $resource, $privilege->getId(), $assert); } } } } return $this; } /** * Remove default ACL resources and rules defined in a module. * Does not save the acl automatically, must call save() to persist. * * @param P4Cms_Module $module the module whose ACL resources and rules need to be removed * @return P4Cms_Acl provides fluent interface. */ public function removeModuleDefaults(P4Cms_Module $module) { // extract resources from package info. $info = $module->getPackageInfo(); $resources = isset($info['acl']) && is_array($info['acl']) ? $info['acl'] : array(); // remove resources from acl. foreach ($resources as $resourceId => $resourceInfo) { // do nothing if resource doesn't exist. if (!$this->has($resourceId)) { continue; } $resource = $this->get($resourceId); // remove module default privileges from resource. $privileges = isset($resourceInfo['privileges']) && is_array($resourceInfo['privileges']) ? $resourceInfo['privileges'] : array(); foreach ($privileges as $key => $value) { // remove the privilege. $resource->removePrivilege($key); } // remove resource if it does not have any privileges. if (!$resource->hasPrivileges()) { $this->remove($resource); } } return $this; } }