From f8d3dec5da7c6585dfb8ad05dde539a7c8706aab Mon Sep 17 00:00:00 2001 From: Demian Katz <demian.katz@villanova.edu> Date: Fri, 1 Sep 2017 08:32:43 -0400 Subject: [PATCH] Added configurable denied permission behavior. - See permissionBehavior.ini for new options - Adds new helper classes for managing permissions - Adds new controller plugin for permission checks - Adds new view helper for permission checks --- config/vufind/permissionBehavior.ini | 72 ++++++ module/VuFind/config/module.config.php | 3 + .../src/VuFind/Controller/AbstractBase.php | 25 ++- .../src/VuFind/Controller/Plugin/Factory.php | 30 ++- .../VuFind/Controller/Plugin/Permission.php | 120 ++++++++++ .../VuFind/Role/PermissionDeniedManager.php | 203 +++++++++++++++++ .../src/VuFind/Role/PermissionManager.php | 110 +++++++++ module/VuFind/src/VuFind/Service/Factory.php | 32 +++ .../src/VuFind/View/Helper/Root/Factory.php | 16 ++ .../VuFind/View/Helper/Root/Permission.php | 128 +++++++++++ .../Role/PermissionDeniedManagerTest.php | 153 +++++++++++++ .../VuFindTest/Role/PermissionManagerTest.php | 132 +++++++++++ .../View/Helper/Root/PermissionTest.php | 208 ++++++++++++++++++ themes/root/theme.config.php | 1 + 14 files changed, 1213 insertions(+), 20 deletions(-) create mode 100644 config/vufind/permissionBehavior.ini create mode 100644 module/VuFind/src/VuFind/Controller/Plugin/Permission.php create mode 100644 module/VuFind/src/VuFind/Role/PermissionDeniedManager.php create mode 100644 module/VuFind/src/VuFind/Role/PermissionManager.php create mode 100644 module/VuFind/src/VuFind/View/Helper/Root/Permission.php create mode 100644 module/VuFind/tests/unit-tests/src/VuFindTest/Role/PermissionDeniedManagerTest.php create mode 100644 module/VuFind/tests/unit-tests/src/VuFindTest/Role/PermissionManagerTest.php create mode 100644 module/VuFind/tests/unit-tests/src/VuFindTest/View/Helper/Root/PermissionTest.php diff --git a/config/vufind/permissionBehavior.ini b/config/vufind/permissionBehavior.ini new file mode 100644 index 00000000000..d9250bd4c3b --- /dev/null +++ b/config/vufind/permissionBehavior.ini @@ -0,0 +1,72 @@ +; This file controls how denied permissions are treated within VuFind. +; +; The permissions need to be set in permissions.ini. +; In this file you can configure what should happen if a permission is denied. +; Please use the permission names from permissions.ini as section names here. +; +; Example: +; +; This role from permissions.ini: +; +; [default.EITModule] +; role = loggedin +; permission = access.EITModule +; +; should be configured here in section: +; +; [access.EITModule] +; +; See permissions.ini for more permissions that you may also wish to configure here. +; +; Within each section, there are two options which can be configured: +; +; deniedTemplateBehavior: The behavior to apply when a permission is denied +; dynamically within a template; i.e. the user has access to a page but is blocked +; from a particular feature of that page. If undefined, the default behavior is +; to hide the affected functionality and display no special message. +; +; deniedControllerBehavior: The behavior to apply when a permission is denied at the +; controller level; i.e. the user is totally blocked from accessing something. If +; undefined, the default behavior defined by the controller (usually promptLogin) +; will be applied. +; +; Each of these options may be set to one of the following options; most options +; also receive a colon-delimited list of parameters. +; +; exception - Throw the specified exception class (param 1) with the specified +; exception message (param 2). +; This option is ONLY supported by deniedControllerBehavior. +; example: deniedControllerBehavior = exception:MyExceptionClass:Access Denied. +; +; promptLogin - Redirect the user to the login page. You can optionally set the +; first parameter to a message to display on the login page (omit for default). +; This option is ONLY supported by deniedControllerBehavior. +; example: deniedControllerBehavior = promptLogin +; +; showMessage - Display the message (the first parameter) to the user; this text +; will be run through the translator. When used as the deniedControllerBehavior, +; the message will be displayed in the form of a flash message on the +; "permission denied" screen. When used as the deniedTemplateBehavior, the text +; will be displayed in place of the blocked markup. +; example: deniedControllerBehavior = showMessage:Blocked! +; +; showTemplate - Render a specific template, specified as the first parameter. +; This option is ONLY supported by deniedTemplateBehavior. +; example: deniedTemplateBehavior = showTemplate:error/denied + +; Section for global parameters +[global] +; The default behavior will get used if a permission is denied, but the controller +; in question defines no default behavior and no permission denied behavior has been +; configured in this file for the relevant permission. +defaultDeniedControllerBehavior = "promptLogin" + +; The default behavior will get used if a permission is denied, but no permission +; denied behavior has been configured in this file for the relevant permission. +; (False means "use the default behavior defined by the template"). +defaultDeniedTemplateBehavior = false + +; Example configuration for non-standard favorites permission behavior: +;[feature.Favorites] +;deniedTemplateBehavior = "showMessage:Login for Favorites" +;deniedControllerBehavior = "exception::You are not logged in!" diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php index 314f7c9631a..77222f57558 100644 --- a/module/VuFind/config/module.config.php +++ b/module/VuFind/config/module.config.php @@ -159,6 +159,7 @@ $config = [ 'holds' => 'VuFind\Controller\Plugin\Factory::getHolds', 'newitems' => 'VuFind\Controller\Plugin\Factory::getNewItems', 'ILLRequests' => 'VuFind\Controller\Plugin\Factory::getILLRequests', + 'permission' => 'VuFind\Controller\Plugin\Factory::getPermission', 'recaptcha' => 'VuFind\Controller\Plugin\Factory::getRecaptcha', 'reserves' => 'VuFind\Controller\Plugin\Factory::getReserves', 'result-scroller' => 'VuFind\Controller\Plugin\Factory::getResultScroller', @@ -218,6 +219,8 @@ $config = [ 'VuFind\RecordTabPluginManager' => 'VuFind\Service\Factory::getRecordTabPluginManager', 'VuFind\RelatedPluginManager' => 'VuFind\Service\Factory::getRelatedPluginManager', 'VuFind\ResolverDriverPluginManager' => 'VuFind\Service\Factory::getResolverDriverPluginManager', + 'VuFind\Role\PermissionManager' => 'VuFind\Service\Factory::getPermissionManager', + 'VuFind\Role\PermissionDeniedManager' => 'VuFind\Service\Factory::getPermissionDeniedManager', 'VuFind\Search' => 'VuFind\Service\Factory::getSearchService', 'VuFind\Search\BackendManager' => 'VuFind\Service\Factory::getSearchBackendManager', 'VuFind\Search\Memory' => 'VuFind\Service\Factory::getSearchMemory', diff --git a/module/VuFind/src/VuFind/Controller/AbstractBase.php b/module/VuFind/src/VuFind/Controller/AbstractBase.php index 000821ae7cf..1236a31c84c 100644 --- a/module/VuFind/src/VuFind/Controller/AbstractBase.php +++ b/module/VuFind/src/VuFind/Controller/AbstractBase.php @@ -60,11 +60,14 @@ class AbstractBase extends AbstractActionController protected $accessPermission = false; /** - * Behavior when access is denied. Valid values are 'promptLogin' and 'exception' + * Behavior when access is denied (used unless overridden through + * permissionBehavior.ini). Valid values are 'promptLogin' and 'exception'. + * Leave at null to use the defaultDeniedControllerBehavior set in + * permissionBehavior.ini (normally 'promptLogin' unless changed). * * @var string */ - protected $accessDeniedBehavior = 'promptLogin'; + protected $accessDeniedBehavior = null; /** * Constructor @@ -85,17 +88,15 @@ class AbstractBase extends AbstractActionController */ public function validateAccessPermission(MvcEvent $e) { - // Make sure the current user has permission to access the module: - if ($this->accessPermission - && !$this->getAuthorizationService()->isGranted($this->accessPermission) - ) { - if ($this->accessDeniedBehavior == 'promptLogin') { - if (!$this->getUser()) { - $e->setResponse($this->forceLogin(null, [], false)); - return; - } + // If there is an access permission set for this controller, pass it + // through the permission helper, and if the helper returns a custom + // response, use that instead of the normal behavior. + if ($this->accessPermission) { + $response = $this->permission() + ->check($this->accessPermission, $this->accessDeniedBehavior); + if (is_object($response)) { + $e->setResponse($response); } - throw new ForbiddenException('Access denied.'); } } diff --git a/module/VuFind/src/VuFind/Controller/Plugin/Factory.php b/module/VuFind/src/VuFind/Controller/Plugin/Factory.php index 63c7261e64f..c84dec47594 100644 --- a/module/VuFind/src/VuFind/Controller/Plugin/Factory.php +++ b/module/VuFind/src/VuFind/Controller/Plugin/Factory.php @@ -103,12 +103,27 @@ class Factory ); } + /** + * Construct the ILLRequests plugin. + * + * @param ServiceManager $sm Service manager. + * + * @return ILLRequests + */ + public static function getILLRequests(ServiceManager $sm) + { + return new ILLRequests( + $sm->getServiceLocator()->get('VuFind\HMAC'), + $sm->getServiceLocator()->get('VuFind\SessionManager') + ); + } + /** * Construct the NewItems plugin. * * @param ServiceManager $sm Service manager. * - * @return Reserves + * @return NewItems */ public static function getNewItems(ServiceManager $sm) { @@ -119,18 +134,17 @@ class Factory } /** - * Construct the ILLRequests plugin. + * Construct the Permission plugin. * * @param ServiceManager $sm Service manager. * - * @return ILLRequests + * @return Permission */ - public static function getILLRequests(ServiceManager $sm) + public static function getPermission(ServiceManager $sm) { - return new ILLRequests( - $sm->getServiceLocator()->get('VuFind\HMAC'), - $sm->getServiceLocator()->get('VuFind\SessionManager') - ); + $pdm = $sm->getServiceLocator()->get('VuFind\Role\PermissionDeniedManager'); + $pm = $sm->getServiceLocator()->get('VuFind\Role\PermissionManager'); + return new Permission($pm, $pdm); } /** diff --git a/module/VuFind/src/VuFind/Controller/Plugin/Permission.php b/module/VuFind/src/VuFind/Controller/Plugin/Permission.php new file mode 100644 index 00000000000..ba91c34ac28 --- /dev/null +++ b/module/VuFind/src/VuFind/Controller/Plugin/Permission.php @@ -0,0 +1,120 @@ +<?php +/** + * VuFind Action Helper - Permission Checker + * + * PHP version 5 + * + * Copyright (C) Villanova University 2017. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Controller_Plugins + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ +namespace VuFind\Controller\Plugin; +use VuFind\I18n\Translator\TranslatorAwareInterface; +use VuFind\Role\PermissionDeniedManager; +use VuFind\Role\PermissionManager; +use Zend\Log\LoggerAwareInterface; +use Zend\Mvc\Controller\Plugin\AbstractPlugin; + +/** + * VuFind Action Helper - Permission Checker + * + * @category VuFind + * @package Controller_Plugins + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Page + */ +class Permission extends AbstractPlugin implements LoggerAwareInterface, + TranslatorAwareInterface +{ + use \VuFind\I18n\Translator\TranslatorAwareTrait; + use \VuFind\Log\LoggerAwareTrait; + + /** + * Permission manager + * + * @var PermissionManager + */ + protected $permissionManager; + + /** + * Permission denied manager + * + * @var PermissionDeniedManager + */ + protected $permissionDeniedManager; + + /** + * Constructor + * + * @param PermissionManager $pm Permission Manager + * @param PermissionDeniedManager $pdm Permission Denied Manager + */ + public function __construct(PermissionManager $pm, PermissionDeniedManager $pdm) + { + $this->permissionManager = $pm; + $this->permissionDeniedManager = $pdm; + } + + /** + * Check if a permission is denied; if so, throw an exception or return an + * error response as configured in permissionBehavior.ini. + * + * @param string $permission Permission to check + * @param string $defaultBehavior Default behavior to use if none configured + * (null to use default configured in the manager, false to take no action). + * + * @return mixed + */ + public function check($permission, $defaultBehavior = null) + { + // Make sure the current user has permission to access the module: + if ($this->permissionManager->isAuthorized($permission) !== true) { + $dl = $this->permissionDeniedManager->getDeniedControllerBehavior( + $permission, $defaultBehavior + ); + $exceptionDescription = isset($dl['exceptionMessage']) + ? $dl['exceptionMessage'] : 'Access denied.'; + switch (strtolower($dl['action'])) { + case 'promptlogin': + $msg = empty($dl['value']) ? null : $dl['value']; + return $this->getController()->forceLogin($msg, [], false); + case 'showmessage': + return $this->getController()->redirect()->toRoute( + 'error-permissiondenied', [], + ['query' => ['msg' => $dl['value']]] + ); + case 'exception': + $exceptionClass + = (isset($dl['value']) && class_exists($dl['value'])) + ? $dl['value'] : 'VuFind\Exception\Forbidden'; + $exception = new $exceptionClass($exceptionDescription); + if ($exception instanceof \Exception) { + throw $exception; + } + $this->logError("Permission configuration problem."); + throw new \Exception("$exceptionClass is not an exception!"); + default: + throw new ForbiddenException($exceptionDescription); + } + } + return null; + } +} diff --git a/module/VuFind/src/VuFind/Role/PermissionDeniedManager.php b/module/VuFind/src/VuFind/Role/PermissionDeniedManager.php new file mode 100644 index 00000000000..a08e305bec1 --- /dev/null +++ b/module/VuFind/src/VuFind/Role/PermissionDeniedManager.php @@ -0,0 +1,203 @@ +<?php +/** + * Permission Manager + * + * PHP version 5 + * + * Copyright (C) Villanova University 2010. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Authorization + * @author Demian Katz <demian.katz@villanova.edu> + * @author Oliver Goldschmidt <o.goldschmidt@tuhh.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org/wiki/ Wiki + */ +namespace VuFind\Role; + +/** + * Permission Manager + * + * @category VuFind + * @package Authorization + * @author Demian Katz <demian.katz@villanova.edu> + * @author Oliver Goldschmidt <o.goldschmidt@tuhh.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org/wiki/ Wiki + */ +class PermissionDeniedManager +{ + /** + * List config + * + * @var array + */ + protected $config; + + /** + * Default behavior for denied permissions at the controller level. + * + * @var string|bool + */ + protected $defaultDeniedControllerBehavior = 'promptLogin'; + + /** + * Default behavior for denied permissions at the template level. + * (False means "do nothing"). + * + * @var string|bool + */ + protected $defaultDeniedTemplateBehavior = false; + + /** + * Constructor + * + * @param array $config configuration + */ + public function __construct($config) + { + $this->config = $config; + // if the config contains a defaultDeniedControllerBehavior setting, apply it + if (isset($config['global']['defaultDeniedControllerBehavior'])) { + $this->defaultDeniedControllerBehavior + = $config['global']['defaultDeniedControllerBehavior']; + } + // if the config contains a defaultDeniedTemplateBehavior setting, apply it + if (isset($config['global']['defaultDeniedTemplateBehavior'])) { + $this->defaultDeniedTemplateBehavior + = $config['global']['defaultDeniedTemplateBehavior']; + } + } + + /** + * Set the default behavior for a denied controller permission + * + * @param string|bool $value Default behavior for a denied controller permission + * + * @return void + */ + public function setDefaultDeniedControllerBehavior($value) + { + $this->defaultDeniedControllerBehavior = $value; + } + + /** + * Set the default behavior for a denied template permission + * + * @param string|bool $value Default behavior for a denied template permission + * + * @return void + */ + public function setDefaultDeniedTemplateBehavior($value) + { + $this->defaultDeniedTemplateBehavior = $value; + } + + /** + * Get behavior to apply when a controller denies a permission. + * + * @param string $permission Permission that has been denied + * @param string $defaultBehavior Default behavior to use if none configured + * (null to use default configured in this class, false to take no action). + * + * @return array|bool Associative array of behavior for the given + * permission (containing the keys 'action', 'value', 'params' and + * 'exceptionMessage' for exceptions) or false if no action needed. + */ + public function getDeniedControllerBehavior($permission, $defaultBehavior = null) + { + if ($defaultBehavior === null) { + $defaultBehavior = $this->defaultDeniedControllerBehavior; + } + return $this->getDeniedBehavior( + $permission, 'deniedControllerBehavior', $defaultBehavior + ); + } + + /** + * Get behavior to apply when a template denies a permission. + * + * @param string $permission Permission that has been denied + * @param string $defaultBehavior Default action to use if none configured + * (null to use default configured in this class, false to take no action). + * + * @return array|bool + */ + public function getDeniedTemplateBehavior($permission, $defaultBehavior = null) + { + if ($defaultBehavior === null) { + $defaultBehavior = $this->defaultDeniedTemplateBehavior; + } + return $this->getDeniedBehavior( + $permission, 'deniedTemplateBehavior', $defaultBehavior + ); + } + + /** + * Get permission denied logic + * + * @param string $permission Permission that has been denied + * @param string $mode Mode of the operation. Should be either + * deniedControllerBehavior or deniedTemplateBehavior + * @param string $defaultBehavior Default action to use if none configured + * + * @return array|bool + */ + protected function getDeniedBehavior($permission, $mode, $defaultBehavior) + { + $config = isset($this->config[$permission][$mode]) + ? $this->config[$permission][$mode] : $defaultBehavior; + + return empty($config) ? false : $this->processConfigString($config); + } + + /** + * Translate a configuration string into an array. + * + * @param string $config Configuration string to process + * + * @return array + */ + protected function processConfigString($config) + { + // Split config string: + $parts = explode(':', $config); + + // Load standard values: + $output = [ + 'action' => array_shift($parts), + 'value' => array_shift($parts), + ]; + + // Special case -- extra parameters for exceptions: + if (strtolower($output['action']) === 'exception') { + $output['exceptionMessage'] = array_shift($parts); + } + + // Now process any remaining keypairs: + $params = []; + while ($param = array_shift($parts)) { + $paramParts = explode('=', $param, 2); + if (count($paramParts) == 2) { + $params[$paramParts[0]] = $paramParts[1]; + } else { + $params[] = $paramParts[0]; + } + } + $output['params'] = $params; + return $output; + } +} diff --git a/module/VuFind/src/VuFind/Role/PermissionManager.php b/module/VuFind/src/VuFind/Role/PermissionManager.php new file mode 100644 index 00000000000..4ec82c7ba8b --- /dev/null +++ b/module/VuFind/src/VuFind/Role/PermissionManager.php @@ -0,0 +1,110 @@ +<?php +/** + * Permission Manager + * + * PHP version 5 + * + * Copyright (C) Villanova University 2010. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Authorization + * @author Demian Katz <demian.katz@villanova.edu> + * @author Oliver Goldschmidt <o.goldschmidt@tuhh.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org/wiki/ Wiki + */ +namespace VuFind\Role; +use ZfcRbac\Service\AuthorizationServiceAwareTrait; + +/** + * Permission Manager + * + * @category VuFind + * @package Authorization + * @author Demian Katz <demian.katz@villanova.edu> + * @author Oliver Goldschmidt <o.goldschmidt@tuhh.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org/wiki/ Wiki + */ +class PermissionManager +{ + use AuthorizationServiceAwareTrait; + + /** + * List config + * + * @var array + */ + protected $config; + + /** + * Constructor + * + * @param array $config configuration + */ + public function __construct(array $config) + { + $this->config = $config; + } + + /** + * Determine if the user is authorized in a certain context or not + * + * @param string $context Context for the permission behavior + * + * @return bool + */ + public function isAuthorized($context) + { + $authService = $this->getAuthorizationService(); + + // if no authorization service is available return false + if (!$authService) { + return false; + } + + if ($authService->isGranted($context)) { + return true; + } + + return false; + } + + /** + * Check if a permission rule exists for a given context + * + * @param string $context Context for the permission behavior + * + * @return bool + */ + public function permissionRuleExists($context) + { + foreach ($this->config as $key => $value) { + if (!isset($value['permission'])) { + continue; + } + if ($value['permission'] == $context) { + return true; + } + if (is_array($value['permission']) + && in_array($context, $value['permission']) + ) { + return true; + } + } + return false; + } +} diff --git a/module/VuFind/src/VuFind/Service/Factory.php b/module/VuFind/src/VuFind/Service/Factory.php index 1a0ef93fbcb..cbe060ff0c7 100644 --- a/module/VuFind/src/VuFind/Service/Factory.php +++ b/module/VuFind/src/VuFind/Service/Factory.php @@ -590,6 +590,38 @@ class Factory ); } + /** + * Construct the PermissionDeniedManager. + * + * @param ServiceManager $sm Service manager. + * + * @return \VuFind\Role\PermissionDeniedManager + */ + public static function getPermissionDeniedManager(ServiceManager $sm) + { + return new \VuFind\Role\PermissionDeniedManager( + $sm->get('VuFind\Config')->get('permissionBehavior') + ); + } + + /** + * Construct the PermissionManager. + * + * @param ServiceManager $sm Service manager. + * + * @return \VuFind\Role\PermissionManager + */ + public static function getPermissionManager(ServiceManager $sm) + { + $permManager = new \VuFind\Role\PermissionManager( + $sm->get('VuFind\Config')->get('permissions')->toArray() + ); + $permManager->setAuthorizationService( + $sm->get('ZfcRbac\Service\AuthorizationService') + ); + return $permManager; + } + /** * Construct the RecordDriver Plugin Manager. * diff --git a/module/VuFind/src/VuFind/View/Helper/Root/Factory.php b/module/VuFind/src/VuFind/View/Helper/Root/Factory.php index 682a83d2e70..021e1b7b1f7 100644 --- a/module/VuFind/src/VuFind/View/Helper/Root/Factory.php +++ b/module/VuFind/src/VuFind/View/Helper/Root/Factory.php @@ -234,6 +234,22 @@ class Factory return new GoogleAnalytics($key, $universal); } + /** + * Construct the Permission helper. + * + * @param ServiceManager $sm Service manager. + * + * @return Permission + */ + public static function getPermission(ServiceManager $sm) + { + $ld = new Permission( + $sm->getServiceLocator()->get('VuFind\Role\PermissionManager'), + $sm->getServiceLocator()->get('VuFind\Role\PermissionDeniedManager') + ); + return $ld; + } + /** * Construct the Piwik helper. * diff --git a/module/VuFind/src/VuFind/View/Helper/Root/Permission.php b/module/VuFind/src/VuFind/View/Helper/Root/Permission.php new file mode 100644 index 00000000000..816a128f01b --- /dev/null +++ b/module/VuFind/src/VuFind/View/Helper/Root/Permission.php @@ -0,0 +1,128 @@ +<?php +/** + * Permission helper + * + * PHP version 5 + * + * Copyright (C) Villanova University 2017. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package View_Helpers + * @author Demian Katz <demian.katz@villanova.edu> + * @author Oliver Goldschmidt <o.goldschmidt@tuhh.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org/wiki/ Wiki + */ +namespace VuFind\View\Helper\Root; +use VuFind\Role\PermissionDeniedManager; +use VuFind\Role\PermissionManager; +use Zend\View\Helper\AbstractHelper; + +/** + * Permission helper + * + * @category VuFind + * @package View_Helpers + * @author Demian Katz <demian.katz@villanova.edu> + * @author Oliver Goldschmidt <o.goldschmidt@tuhh.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link http://vufind.org/wiki/ Wiki + */ +class Permission extends AbstractHelper +{ + /** + * PermissionDenied manager for behavior on denied permissions + * + * @var PermissionDeniedManager + */ + protected $permissionDeniedManager; + + /** + * Permission manager to decide if a permission has been granted or not + * + * @var PermissionManager + */ + protected $permissionManager; + + /** + * Constructor + * + * @param PermissionsManager $permissionManager Manager to decide + * if a permission has + * been granted or not + * @param PermissionsDeniedManager $permissionDeniedManager Manager for + * behavior on + * denied permissions + */ + public function __construct( + PermissionManager $permissionManager, + PermissionDeniedManager $permissionDeniedManager + ) { + $this->permissionManager = $permissionManager; + $this->permissionDeniedManager = $permissionDeniedManager; + } + + /** + * Determine if a local block inside the template should be displayed + * + * @param string $context Name of the permission rule + * + * @return bool + */ + public function allowDisplay($context) + { + // If there is no context, assume permission is granted. + if (empty($context)) { + return true; + } + + // If permission has been granted, we do not need to continue + // Just return true to indicate that the default can get applied + if ($this->permissionManager->isAuthorized($context) === true) { + return true; + } + // If we are getting to this point, we know that the permission has been + // denied. Nevertheless show the local block if there is no + // deniedTemplateBehavior set for this context. + $displayLogic = $this->permissionDeniedManager + ->getDeniedTemplateBehavior($context); + return !isset($displayLogic['action']); + } + + /** + * Get content to display in place of blocked content + * + * @param string $context Name of the permission rule + * + * @return string + */ + public function getAlternateContent($context) + { + $displayLogic = $this->permissionDeniedManager + ->getDeniedTemplateBehavior($context); + + switch (isset($displayLogic['action']) ? $displayLogic['action'] : '') { + case 'showMessage': + return $this->view->transEsc($displayLogic['value']); + case 'showTemplate': + return $this->view->context($this->view)->renderInContext( + $displayLogic['value'], $displayLogic['params'] + ); + default: + return null; + } + } +} diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Role/PermissionDeniedManagerTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Role/PermissionDeniedManagerTest.php new file mode 100644 index 00000000000..ee0156e0047 --- /dev/null +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Role/PermissionDeniedManagerTest.php @@ -0,0 +1,153 @@ +<?php +/** + * PermissionManager Test Class + * + * PHP version 5 + * + * Copyright (C) Villanova University 2010. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Tests + * @author Oliver Goldschmidt <@o.goldschmidt@tuhh.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +namespace VuFindTest\Role; +use VuFind\Role\PermissionDeniedManager; + +/** + * PermissionManager Test Class + * + * @category VuFind + * @package Tests + * @author Oliver Goldschmidt <@o.goldschmidt@tuhh.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +class PermissionDeniedManagerTest extends \VuFindTest\Unit\TestCase +{ + /** + * Sample configuration with varios config options. + * + * @var array + */ + protected $permissionDeniedConfig = [ + 'permissionDeniedTemplate' => [ + 'deniedTemplateBehavior' => "showTemplate:record/displayLogicTest:param1=noValue", + 'deniedControllerBehavior' => "showTemplate:record/ActionTest:param1=noValue" + ], + 'permissionDeniedTemplateNoParams' => [ + 'deniedTemplateBehavior' => "showTemplate:record/displayLogicTest", + 'deniedControllerBehavior' => "showTemplate:record/ActionTest" + ], + 'permissionDeniedMessage' => [ + 'deniedTemplateBehavior' => "showMessage:dl_translatable_test", + 'deniedControllerBehavior' => "showTemplate:action_translatable_test" + ], + 'permissionDeniedLogin' => [ + 'deniedControllerBehavior' => "promptLogin" + ], + 'permissionDeniedException' => [ + 'deniedControllerBehavior' => "exception:ForbiddenException:exception_message" + ], + 'permissionDeniedNonExistentException' => [ + 'deniedControllerBehavior' => "exception:NonExistentException:exception_message" + ], + 'permissionDeniedNothing' => [ + ], + ]; + + /** + * Test a correctly configured template + * + * @return void + */ + public function testTemplateConfig() + { + $expected = [ + 'action' => 'showTemplate', + 'value' => 'record/ActionTest', + 'params' => [ + 'param1' => 'noValue', + ], + ]; + $expectedNoParams = [ + 'action' => 'showTemplate', + 'value' => 'record/ActionTest', + 'params' => [], + ]; + $pm = new PermissionDeniedManager($this->permissionDeniedConfig); + + $this->assertEquals($expected, $pm->getDeniedControllerBehavior('permissionDeniedTemplate')); + $this->assertEquals($expectedNoParams, $pm->getDeniedControllerBehavior('permissionDeniedTemplateNoParams')); + } + + /** + * Test a correctly configured exception + * + * @return void + */ + public function testExceptionConfig() + { + $expected = [ + 'action' => 'exception', + 'value' => 'ForbiddenException', + 'exceptionMessage' => 'exception_message', + 'params' => [], + ]; + $pm = new PermissionDeniedManager($this->permissionDeniedConfig); + + $this->assertEquals($expected, $pm->getDeniedControllerBehavior('permissionDeniedException')); + } + + /** + * Test an empty permission section + * getDeniedControllerBehavior should return false as the PermissionDeniedManager + * has nothing to do + * + * @return void + */ + public function testEmptyConfig() + { + $expected = [ + 'action' => 'promptLogin', + 'value' => false, + 'params' => [], + ]; + $pm = new PermissionDeniedManager($this->permissionDeniedConfig); + + $this->assertEquals($expected, $pm->getDeniedControllerBehavior('permissionDeniedNothing')); + } + + /** + * Test a non existent permission section + * getDeniedControllerBehavior should return false as the PermissionDeniedManager + * has nothing to do + * + * @return void + */ + public function testNonExistentConfig() + { + $expected = [ + 'action' => 'promptLogin', + 'value' => false, + 'params' => [], + ]; + $pm = new PermissionDeniedManager($this->permissionDeniedConfig); + + $this->assertEquals($expected, $pm->getDeniedControllerBehavior('garbage')); + } +} diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Role/PermissionManagerTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Role/PermissionManagerTest.php new file mode 100644 index 00000000000..7c08377cdf6 --- /dev/null +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Role/PermissionManagerTest.php @@ -0,0 +1,132 @@ +<?php +/** + * PermissionManager Test Class + * + * PHP version 5 + * + * Copyright (C) Villanova University 2010. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Tests + * @author Oliver Goldschmidt <@o.goldschmidt@tuhh.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +namespace VuFindTest\Role; +use VuFind\Role\PermissionManager; + +/** + * PermissionManager Test Class + * + * @category VuFind + * @package Tests + * @author Oliver Goldschmidt <@o.goldschmidt@tuhh.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +class PermissionManagerTest extends \VuFindTest\Unit\TestCase +{ + /** + * Sample configuration with varios config options. + * + * @var array + */ + protected $permissionConfig = [ + 'permission.all' => [ + 'permission' => "everyone" + ], + 'permission.nobody' => [ + 'permission' => "nobody" + ], + 'permission.empty' => [ + ], + 'permission.array' => [ + 'permission' => ['everyoneArray', 'everyoneArray2'] + ] + ]; + + /** + * Test a non existent permission section + * + * @return void + */ + public function testNonExistentPermission() + { + $pm = new PermissionManager($this->permissionConfig); + + $this->assertEquals(false, $pm->permissionRuleExists('garbage')); + } + + /** + * Test an existing permission section + * + * @return void + */ + public function testExistentPermission() + { + $pm = new PermissionManager($this->permissionConfig); + + $this->assertEquals(true, $pm->permissionRuleExists('everyone')); + } + + /** + * Test an existing permission section in an array + * + * @return void + */ + public function testExistentPermissionInArray() + { + $pm = new PermissionManager($this->permissionConfig); + + $this->assertEquals(true, $pm->permissionRuleExists('everyoneArray')); + } + + /** + * Test a granted permission + * + * @return void + */ + public function testGrantedPermission() + { + $pm = new PermissionManager($this->permissionConfig); + $mockAuth = $this->getMockBuilder('ZfcRbac\Service\AuthorizationService') + ->disableOriginalConstructor() + ->getMock(); + $mockAuth->expects($this->any())->method('isGranted') + ->will($this->returnValue(true)); + $pm->setAuthorizationService($mockAuth); + + $this->assertEquals(true, $pm->isAuthorized('permission.everyone')); + } + + /** + * Test a denied permission + * + * @return void + */ + public function testDeniedPermission() + { + $pm = new PermissionManager($this->permissionConfig); + $mockAuth = $this->getMockBuilder('ZfcRbac\Service\AuthorizationService') + ->disableOriginalConstructor() + ->getMock(); + $mockAuth->expects($this->any())->method('isGranted') + ->will($this->returnValue(false)); + $pm->setAuthorizationService($mockAuth); + + $this->assertEquals(false, $pm->isAuthorized('permission.nobody')); + } +} diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/View/Helper/Root/PermissionTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/View/Helper/Root/PermissionTest.php new file mode 100644 index 00000000000..76154cf2f8e --- /dev/null +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/View/Helper/Root/PermissionTest.php @@ -0,0 +1,208 @@ +<?php +/** + * Permission view helper Test Class + * + * PHP version 5 + * + * Copyright (C) Villanova University 2010. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Tests + * @author Demian Katz <demian.katz@villanova.edu> + * @author Oliver Goldschmidt <o.goldschmidt@tuhh.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +namespace VuFindTest\View\Helper\Root; +use VuFind\View\Helper\Root\Permission; + +/** + * Permission view helper Test Class + * + * @category VuFind + * @package Tests + * @author Demian Katz <demian.katz@villanova.edu> + * @author Oliver Goldschmidt <o.goldschmidt@tuhh.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +class PermissionTest extends \VuFindTest\Unit\ViewHelperTestCase +{ + /** + * Sample configuration with varios config options. + * + * @var array + */ + protected $permissionDeniedConfig = [ + 'permissionDeniedTemplate' => [ + 'deniedTemplateBehavior' => "showTemplate:record/displayLogicTest:param1=noValue", + 'deniedControllerBehavior' => "showTemplate:record/ActionTest:param1=noValue" + ], + 'permissionDeniedTemplateNoParams' => [ + 'deniedTemplateBehavior' => "showTemplate:record/displayLogicTest", + 'deniedControllerBehavior' => "showTemplate:record/ActionTest" + ], + 'permissionDeniedMessage' => [ + 'deniedTemplateBehavior' => "showMessage:dl_translatable_test", + 'deniedControllerBehavior' => "showTemplate:action_translatable_test" + ], + 'permissionDeniedLogin' => [ + 'deniedControllerBehavior' => "promptLogin" + ], + 'permissionDeniedException' => [ + 'deniedControllerBehavior' => "exception:ForbiddenException:exception_message" + ], + 'permissionDeniedNonExistentException' => [ + 'deniedControllerBehavior' => "exception:NonExistentException:exception_message" + ], + 'permissionDeniedNothing' => [ + ], + ]; + + /** + * Test the message display + * + * @return void + */ + public function testMessageDisplay() + { + $mockPmdMessage = $this->getMockPmd([ + 'deniedTemplateBehavior' => [ + 'action' => 'showMessage', + 'value' => 'dl_translatable_test', + 'params' => [], + ], + ]); + + $helper = new Permission($this->getMockPm(false), $mockPmdMessage); + $helper->setView($this->getMockView()); + + $displayBlock = $helper->getAlternateContent('permissionDeniedMessage'); + $this->assertEquals('dl_translatable_test', $displayBlock); + } + + /** + * Test the template display + * + * @return void + */ + public function testTemplateDisplay() + { + // Template does not exist, expect an exception, though + $this->setExpectedException('Zend\View\Exception\RuntimeException'); + + $mockPmd = $this->getMockPmd([ + 'deniedTemplateBehavior' => [ + 'action' => 'showTemplate', + 'value' => 'record/displayLogicTest', + 'params' => [], + ], + ]); + + $helper = new Permission($this->getMockPm(false), $mockPmd); + $helper->setView($this->getMockView()); + + $displayBlock = $helper->getAlternateContent('permissionDeniedTemplate'); + } + + /** + * Test the template display with an existing template + * + * @return void + */ + public function testExistingTemplateDisplay() + { + $mockPmd = $this->getMockPmd([ + 'deniedTemplateBehavior' => [ + 'action' => 'showTemplate', + 'value' => 'ajax/status-available.phtml', + 'params' => [], + ], + ]); + + $helper = new Permission($this->getMockPm(false), $mockPmd); + $helper->setView($this->getMockView()); + + $this->assertEquals( + '<span class="label label-success">Available</span>', + trim($helper->getAlternateContent('permissionDeniedTemplate')) + ); + } + + /** + * Get mock driver that returns a deniedTemplateBehavior. + * + * @param array $config Config containing DeniedTemplateBehavior to return + * + * @return \VuFind\Role\PermissionDeniedManager + */ + protected function getMockPmd($config = false) { + $mockPmd = $this->getMockBuilder('\VuFind\Role\PermissionDeniedManager') + ->setConstructorArgs([$this->permissionDeniedConfig]) + ->getMock(); + $mockPmd->expects($this->any())->method('getDeniedTemplateBehavior') + ->will($this->returnValue($config['deniedTemplateBehavior'])); + return $mockPmd; + } + + /** + * Get mock permission manager + * + * @param array $isAuthorized isAuthorized value to return + * + * @return \VuFind\Role\PermissionManager + */ + protected function getMockPm($isAuthorized = false) { + $mockPm = $this->getMockBuilder('\VuFind\Role\PermissionManager') + ->disableOriginalConstructor() + ->getMock(); + $mockPm->expects($this->any())->method('isAuthorized') + ->will($this->returnValue($isAuthorized)); + $mockPm->expects($this->any())->method('permissionRuleExists') + ->will($this->returnValue(true)); + + return $mockPm; + } + + /** + * Get mock context helper. + * + * @return \VuFind\View\Helper\Root\Context + */ + protected function getMockContext() + { + return $this->getMockBuilder('VuFind\View\Helper\Root\Context') + ->disableOriginalConstructor()->getMock(); + } + + /** + * Return a view object populated for these test cases. + * + * @return \Zend\View\Renderer\PhpRenderer + */ + protected function getMockView() + { + $escapehtml = new \Zend\View\Helper\EscapeHtml(); + $translate = new \VuFind\View\Helper\Root\Translate(); + $transEsc = new \VuFind\View\Helper\Root\TransEsc(); + $context = new \VuFind\View\Helper\Root\Context(); + $realView = $this->getPhpRenderer( + compact('translate', 'transEsc', 'context', 'escapehtml') + ); + $transEsc->setView($realView); + return $realView; + } +} diff --git a/themes/root/theme.config.php b/themes/root/theme.config.php index a6d343b3517..79082aaddbf 100644 --- a/themes/root/theme.config.php +++ b/themes/root/theme.config.php @@ -22,6 +22,7 @@ return array( 'ils' => 'VuFind\View\Helper\Root\Factory::getIls', 'jstranslations' => 'VuFind\View\Helper\Root\Factory::getJsTranslations', 'keepalive' => 'VuFind\View\Helper\Root\Factory::getKeepAlive', + 'permission' => 'VuFind\View\Helper\Root\Factory::getPermission', 'proxyurl' => 'VuFind\View\Helper\Root\Factory::getProxyUrl', 'openurl' => 'VuFind\View\Helper\Root\Factory::getOpenUrl', 'piwik' => 'VuFind\View\Helper\Root\Factory::getPiwik', -- GitLab