From 9567a6086e5feb25225f66330f5467b397a649a3 Mon Sep 17 00:00:00 2001
From: Demian Katz <demian.katz@villanova.edu>
Date: Fri, 23 Aug 2019 07:15:36 -0400
Subject: [PATCH] Make it easier to globally set controller permissions (#1395)

---
 config/vufind/permissionBehavior.ini          | 12 ++++++
 .../src/VuFind/Controller/AbstractBase.php    | 28 ++++++++++++-
 .../VuFind/Controller/AbstractBaseFactory.php | 42 ++++++++++++++++++-
 .../AbstractBaseWithConfigFactory.php         |  7 ++--
 .../Controller/CartControllerFactory.php      |  8 ++--
 .../Controller/ChannelsControllerFactory.php  |  5 +--
 .../Controller/MyResearchController.php       | 13 ++++++
 .../Controller/UpgradeControllerFactory.php   |  8 ++--
 8 files changed, 108 insertions(+), 15 deletions(-)

diff --git a/config/vufind/permissionBehavior.ini b/config/vufind/permissionBehavior.ini
index 550838fb0c9..3390fd023d8 100644
--- a/config/vufind/permissionBehavior.ini
+++ b/config/vufind/permissionBehavior.ini
@@ -66,6 +66,18 @@ defaultDeniedControllerBehavior = "promptLogin"
 ; (False means "use the default behavior defined by the template").
 defaultDeniedTemplateBehavior = false
 
+; This setting can be used to override access permissions for controllers. You can
+; set a controller class name inside the brackets to explicitly override that
+; controller's access permission (and that of any of its children, unless more
+; specific permissions are set for those subclasses), whether or not it has a
+; default defined in the class. You can use * inside the brackets to set a default
+; access permission that is applied to all controllers that do not have an
+; internally-defined default (the MyResearchController is also excluded from
+; this setting to avoid infinite login redirects). This can be useful if you
+; wish to password-protect your entire site.
+;controllerAccess[*] = access.VuFindInterface
+;controllerAccess[VuFind\Controller\SearchController] = access.SearchResults
+
 ; Example configuration for non-standard favorites permission behavior:
 ;[feature.Favorites]
 ;deniedTemplateBehavior = "showMessage:Login for Favorites"
diff --git a/module/VuFind/src/VuFind/Controller/AbstractBase.php b/module/VuFind/src/VuFind/Controller/AbstractBase.php
index 69fdaf2c5c7..90e9961f4f1 100644
--- a/module/VuFind/src/VuFind/Controller/AbstractBase.php
+++ b/module/VuFind/src/VuFind/Controller/AbstractBase.php
@@ -51,11 +51,12 @@ class AbstractBase extends AbstractActionController
 {
     /**
      * Permission that must be granted to access this module (false for no
-     * restriction)
+     * restriction, null to use configured default (which is usually the same
+     * as false)).
      *
      * @var string|bool
      */
-    protected $accessPermission = false;
+    protected $accessPermission = null;
 
     /**
      * Behavior when access is denied (used unless overridden through
@@ -105,6 +106,29 @@ class AbstractBase extends AbstractActionController
         }
     }
 
+    /**
+     * Getter for access permission.
+     *
+     * @return string|bool
+     */
+    public function getAccessPermission()
+    {
+        return $this->accessPermission;
+    }
+
+    /**
+     * Getter for access permission.
+     *
+     * @param string $ap Permission to require for access to the controller (false
+     * for no requirement)
+     *
+     * @return void
+     */
+    public function setAccessPermission($ap)
+    {
+        $this->accessPermission = empty($ap) ? false : $ap;
+    }
+
     /**
      * Register the default events for this controller
      *
diff --git a/module/VuFind/src/VuFind/Controller/AbstractBaseFactory.php b/module/VuFind/src/VuFind/Controller/AbstractBaseFactory.php
index 6f3d621cc8a..f16c84b5708 100644
--- a/module/VuFind/src/VuFind/Controller/AbstractBaseFactory.php
+++ b/module/VuFind/src/VuFind/Controller/AbstractBaseFactory.php
@@ -41,6 +41,46 @@ use Zend\ServiceManager\Factory\FactoryInterface;
  */
 class AbstractBaseFactory implements FactoryInterface
 {
+    /**
+     * Apply permission settings to the controller.
+     *
+     * @param ContainerInterface $container  Service manager
+     * @param AbstractBase       $controller Controller to configure
+     *
+     * @return AbstractBase
+     */
+    protected function applyPermissions($container, $controller)
+    {
+        $config = $container->get(\VuFind\Config\PluginManager::class)
+            ->get('permissionBehavior');
+        $permissions = $config->global->controllerAccess ?? [];
+
+        if (!empty($permissions)) {
+            // Iterate through parent classes until we find the most specific
+            // class access permission defined (if any):
+            $class = get_class($controller);
+            do {
+                if (isset($permissions[$class])) {
+                    $controller->setAccessPermission($permissions[$class]);
+                    break;
+                }
+                $class = get_parent_class($class);
+            } while ($class);
+
+            // If the controller's current permission is null (as opposed to false
+            // or a string), that means it has no internally configured default, and
+            // setAccessPermission was not called above; thus, we should apply the
+            // default value:
+            if (isset($permissions['*'])
+                && $controller->getAccessPermission() === null
+            ) {
+                $controller->setAccessPermission($permissions['*']);
+            }
+        }
+
+        return $controller;
+    }
+
     /**
      * Create an object
      *
@@ -61,6 +101,6 @@ class AbstractBaseFactory implements FactoryInterface
         if (!empty($options)) {
             throw new \Exception('Unexpected options sent to factory.');
         }
-        return new $requestedName($container);
+        return $this->applyPermissions($container, new $requestedName($container));
     }
 }
diff --git a/module/VuFind/src/VuFind/Controller/AbstractBaseWithConfigFactory.php b/module/VuFind/src/VuFind/Controller/AbstractBaseWithConfigFactory.php
index b6a6f5c731f..f9efa5a7e22 100644
--- a/module/VuFind/src/VuFind/Controller/AbstractBaseWithConfigFactory.php
+++ b/module/VuFind/src/VuFind/Controller/AbstractBaseWithConfigFactory.php
@@ -28,7 +28,6 @@
 namespace VuFind\Controller;
 
 use Interop\Container\ContainerInterface;
-use Zend\ServiceManager\Factory\FactoryInterface;
 
 /**
  * Generic controller factory (with config injection).
@@ -39,7 +38,7 @@ use Zend\ServiceManager\Factory\FactoryInterface;
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     https://vufind.org/wiki/development Wiki
  */
-class AbstractBaseWithConfigFactory implements FactoryInterface
+class AbstractBaseWithConfigFactory extends AbstractBaseFactory
 {
     /**
      * Create an object
@@ -63,6 +62,8 @@ class AbstractBaseWithConfigFactory implements FactoryInterface
         }
         $config = $container->get(\VuFind\Config\PluginManager::class)
             ->get('config');
-        return new $requestedName($container, $config);
+        return $this->applyPermissions(
+            $container, new $requestedName($container, $config)
+        );
     }
 }
diff --git a/module/VuFind/src/VuFind/Controller/CartControllerFactory.php b/module/VuFind/src/VuFind/Controller/CartControllerFactory.php
index bb56f89a8b9..5e5a908056d 100644
--- a/module/VuFind/src/VuFind/Controller/CartControllerFactory.php
+++ b/module/VuFind/src/VuFind/Controller/CartControllerFactory.php
@@ -28,7 +28,6 @@
 namespace VuFind\Controller;
 
 use Interop\Container\ContainerInterface;
-use Zend\ServiceManager\Factory\FactoryInterface;
 
 /**
  * Cart controller factory.
@@ -39,7 +38,7 @@ use Zend\ServiceManager\Factory\FactoryInterface;
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     https://vufind.org/wiki/development Wiki
  */
-class CartControllerFactory implements FactoryInterface
+class CartControllerFactory extends AbstractBaseFactory
 {
     /**
      * Create an object
@@ -64,6 +63,9 @@ class CartControllerFactory implements FactoryInterface
         $session = new \Zend\Session\Container(
             'cart_followup', $container->get(\Zend\Session\SessionManager::class)
         );
-        return new $requestedName($container, $session);
+        return $this->applyPermissions(
+            $container,
+            new $requestedName($container, $session)
+        );
     }
 }
diff --git a/module/VuFind/src/VuFind/Controller/ChannelsControllerFactory.php b/module/VuFind/src/VuFind/Controller/ChannelsControllerFactory.php
index d3ca628582e..28e3c921706 100644
--- a/module/VuFind/src/VuFind/Controller/ChannelsControllerFactory.php
+++ b/module/VuFind/src/VuFind/Controller/ChannelsControllerFactory.php
@@ -28,7 +28,6 @@
 namespace VuFind\Controller;
 
 use Interop\Container\ContainerInterface;
-use Zend\ServiceManager\Factory\FactoryInterface;
 
 /**
  * Channels controller factory.
@@ -39,7 +38,7 @@ use Zend\ServiceManager\Factory\FactoryInterface;
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     https://vufind.org/wiki/development Wiki
  */
-class ChannelsControllerFactory implements FactoryInterface
+class ChannelsControllerFactory extends AbstractBaseFactory
 {
     /**
      * Create an object
@@ -62,6 +61,6 @@ class ChannelsControllerFactory implements FactoryInterface
             throw new \Exception('Unexpected options sent to factory.');
         }
         $loader = $container->get(\VuFind\ChannelProvider\ChannelLoader::class);
-        return new $requestedName($loader);
+        return $this->applyPermissions($container, new $requestedName($loader));
     }
 }
diff --git a/module/VuFind/src/VuFind/Controller/MyResearchController.php b/module/VuFind/src/VuFind/Controller/MyResearchController.php
index 0c3dcad453d..30d7d1e1f18 100644
--- a/module/VuFind/src/VuFind/Controller/MyResearchController.php
+++ b/module/VuFind/src/VuFind/Controller/MyResearchController.php
@@ -49,6 +49,19 @@ use Zend\View\Model\ViewModel;
  */
 class MyResearchController extends AbstractBase
 {
+    /**
+     * Permission that must be granted to access this module (false for no
+     * restriction, null to use configured default (which is usually the same
+     * as false)).
+     *
+     * For this controller, we default to false rather than null because
+     * we don't want a default setting to override the controller's accessibility
+     * and break the login process!
+     *
+     * @var string|bool
+     */
+    protected $accessPermission = false;
+
     /**
      * ILS Pagination Helper
      *
diff --git a/module/VuFind/src/VuFind/Controller/UpgradeControllerFactory.php b/module/VuFind/src/VuFind/Controller/UpgradeControllerFactory.php
index 928b0daeb65..572b9d0c345 100644
--- a/module/VuFind/src/VuFind/Controller/UpgradeControllerFactory.php
+++ b/module/VuFind/src/VuFind/Controller/UpgradeControllerFactory.php
@@ -28,7 +28,6 @@
 namespace VuFind\Controller;
 
 use Interop\Container\ContainerInterface;
-use Zend\ServiceManager\Factory\FactoryInterface;
 
 /**
  * Upgrade controller factory.
@@ -39,7 +38,7 @@ use Zend\ServiceManager\Factory\FactoryInterface;
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     https://vufind.org/wiki/development Wiki
  */
-class UpgradeControllerFactory implements FactoryInterface
+class UpgradeControllerFactory extends AbstractBaseFactory
 {
     /**
      * Create an object
@@ -65,6 +64,9 @@ class UpgradeControllerFactory implements FactoryInterface
         $session = new \Zend\Session\Container(
             'upgrade', $container->get(\Zend\Session\SessionManager::class)
         );
-        return new $requestedName($container, $cookieManager, $session);
+        return $this->applyPermissions(
+            $container,
+            new $requestedName($container, $cookieManager, $session)
+        );
     }
 }
-- 
GitLab