From 2d814a3e9ae79dbebde5705b2e38c16075427229 Mon Sep 17 00:00:00 2001
From: RDS-Team <rds@redi-bw.de>
Date: Tue, 10 Mar 2015 13:45:52 -0400
Subject: [PATCH] Added ServerParam permission provider (and tests).

---
 config/vufind/permissions.ini                 |  33 ++-
 module/VuFind/config/module.config.php        |   1 +
 .../Role/PermissionProvider/Factory.php       |  12 +
 .../Role/PermissionProvider/ServerParam.php   | 204 ++++++++++++++++
 .../PermissionProvider/ServerParamTest.php    | 229 ++++++++++++++++++
 5 files changed, 466 insertions(+), 13 deletions(-)
 create mode 100644 module/VuFind/src/VuFind/Role/PermissionProvider/ServerParam.php
 create mode 100644 module/VuFind/tests/unit-tests/src/VuFindTest/Role/PermissionProvider/ServerParamTest.php

diff --git a/config/vufind/permissions.ini b/config/vufind/permissions.ini
index 734f88ee6f2..87f703e397b 100644
--- a/config/vufind/permissions.ini
+++ b/config/vufind/permissions.ini
@@ -18,18 +18,25 @@
 ; The values associated with these keys will be passed along to the services.
 ; You can define your own permission providers, or use some of the following:
 ;
-; ipRange  - Grant the permission to the single IP adresse or to the range.
-;            Accepts a single IP adresse or a range with a minus character without 
-;            blanks as seperator.
-; ipRegEx  - Grant the permission to IP addresses matching the provided regular
-;            expression(s). Accepts a string or an array; if an array is passed,
-;            permission will be granted if ANY one of the expressions matches.
-; role     - Grant the permission automatically to the role or roles specified
-;            (accepts a string or an array). Note that VuFind uses 'guest' for
-;            logged-out users and 'loggedin' for all logged-in users. You may
-;            define additional roles with custom code.
-; username - Grant the permission to logged-in users whose usernames match the
-;            specified value(s). Accepts a string or an array.
+; ipRange     - Grant the permission to the single IP adresse or to the range.
+;               Accepts a single IP adresse or a range with a minus character without
+;               blanks as seperator.
+; ipRegEx     - Grant the permission to IP addresses matching the provided regular
+;               expression(s). Accepts a string or an array; if an array is passed,
+;               permission will be granted if ANY one of the expressions matches.
+; role        - Grant the permission automatically to the role or roles specified
+;               (accepts a string or an array). Note that VuFind uses 'guest' for
+;               logged-out users and 'loggedin' for all logged-in users. You may
+;               define additional roles with custom code.
+; serverParam - Grant the permission if request server params match the given rules.
+;               Accepts a string or an array; if an array is passed permission will
+;               be granted if ALL of the rules match. Rules are specified as
+;               <server param name> [modifier] <value> [<value 2> ... <value n>]
+;               with optional modifier ~ (match instead of string comparison, values
+;               are treated as regular expressions), ! (not) or !~ (no match). Only
+;               one of the values must match (OR).
+; username    - Grant the permission to logged-in users whose usernames match the
+;               specified value(s). Accepts a string or an array.
 ;
 ; Example configuration (grants the "sample.permission" permission to users named
 ; admin1 or admin2, or anyone coming from the IP addresses 1.2.3.4 or 1.2.3.5):
@@ -52,4 +59,4 @@
 ; Default configuration for the EIT module; see EIT.ini for some notes on this.
 [default.EITModule]
 role = loggedin
-permission = access.EITModule
\ No newline at end of file
+permission = access.EITModule
diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php
index a5dc14c287a..afd315515cb 100644
--- a/module/VuFind/config/module.config.php
+++ b/module/VuFind/config/module.config.php
@@ -674,6 +674,7 @@ $config = [
             'factories' => [
                 'ipRange' => 'VuFind\Role\PermissionProvider\Factory::getIpRange',
                 'ipRegEx' => 'VuFind\Role\PermissionProvider\Factory::getIpRegEx',
+                'serverParam' => 'VuFind\Role\PermissionProvider\Factory::getServerParam',
                 'username' => 'VuFind\Role\PermissionProvider\Factory::getUsername',
             ],
             'invokables' => [
diff --git a/module/VuFind/src/VuFind/Role/PermissionProvider/Factory.php b/module/VuFind/src/VuFind/Role/PermissionProvider/Factory.php
index 463820ae950..f6944195a1a 100644
--- a/module/VuFind/src/VuFind/Role/PermissionProvider/Factory.php
+++ b/module/VuFind/src/VuFind/Role/PermissionProvider/Factory.php
@@ -65,6 +65,18 @@ class Factory
         return new IpRegEx($sm->getServiceLocator()->get('Request'));
     }
 
+    /**
+     * Factory for ServerParam
+     *
+     * @param ServiceManager $sm Service manager.
+     *
+     * @return ServerParam
+     */
+    public static function getServerParam(ServiceManager $sm)
+    {
+        return new ServerParam($sm->getServiceLocator()->get('Request'));
+    }
+
     /**
      * Factory for Username
      *
diff --git a/module/VuFind/src/VuFind/Role/PermissionProvider/ServerParam.php b/module/VuFind/src/VuFind/Role/PermissionProvider/ServerParam.php
new file mode 100644
index 00000000000..615f0751eab
--- /dev/null
+++ b/module/VuFind/src/VuFind/Role/PermissionProvider/ServerParam.php
@@ -0,0 +1,204 @@
+<?php
+/**
+ * ServerParam permission provider for VuFind.
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2007.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Authorization
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @author   Jochen Lienhard <lienhard@ub.uni-freiburg.de>
+ * @author   Bernd Oberknapp <bo@ub.uni-freiburg.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+namespace VuFind\Role\PermissionProvider;
+use Zend\Http\PhpEnvironment\Request;
+
+/**
+ * ServerParam permission provider for VuFind.
+ *
+ * @category VuFind2
+ * @package  Authorization
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @author   Jochen Lienhard <lienhard@ub.uni-freiburg.de>
+ * @author   Bernd Oberknapp <bo@ub.uni-freiburg.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://www.vufind.org  Main Page
+ */
+class ServerParam implements PermissionProviderInterface,
+    \Zend\Log\LoggerAwareInterface
+{
+    use \VuFind\Log\LoggerAwareTrait;
+
+    /**
+     * Request object
+     *
+     * @var Request
+     */
+    protected $request;
+
+    /**
+     * Aliases for server param names (default: none)
+     *
+     * @var array
+     */
+    protected $aliases = [];
+
+    /**
+     * Delimiter for multi-valued server params (default: none)
+     *
+     * @var string
+     */
+    protected $serverParamDelimiter = '';
+
+    /**
+     * Escape character for delimiter in server param strings (default: none)
+     *
+     * @var string
+     */
+    protected $serverParamEscape = '';
+
+    /**
+     * Constructor
+     *
+     * @param Request $request Request object
+     */
+    public function __construct(Request $request)
+    {
+        $this->request = $request;
+    }
+
+    /**
+     * Return an array of roles which may be granted the permission based on
+     * the options.
+     *
+     * @param mixed $options Options provided from configuration.
+     *
+     * @return array
+     */
+    public function getPermissions($options)
+    {
+        // user only gets the permission if all options match (AND)
+        foreach ((array)$options as $option) {
+            $this->debug("getPermissions: option '{$option}'");
+            if (!$this->checkServerParam($option)) {
+                $this->debug("getPermissions: result = false");
+                return [];
+            }
+            $this->debug("getPermissions: result = true");
+        }
+        return ['loggedin'];
+    }
+
+    /**
+     * Check if a server param matches the option.
+     *
+     * @param string $option Option
+     *
+     * @return boolean true if a server param matches, false if not
+     */
+    protected function checkServerParam($option)
+    {
+        // split option on spaces unless escaped with backslash
+        $optionParts = $this->splitString($option, ' ', '\\');
+        if (count($optionParts) < 2) {
+            $this->logError("configuration option '{$option}' invalid");
+            return false;
+        }
+
+        // first part is the server param name
+        $serverParamName = array_shift($optionParts);
+        if (isset($this->aliases[$serverParamName])) {
+            $serverParamName = $this->aliases[$serverParamName];
+        }
+
+        // optional modifier follow server param name
+        $modifierMatch = in_array($optionParts[0], ['~', '!~']);
+        $modifierNot = in_array($optionParts[0], ['!', '!~']);
+        if ($modifierNot || $modifierMatch) {
+            array_shift($optionParts);
+        }
+
+        // remaining parts are the templates for checking the server params
+        $templates = $optionParts;
+        if (empty($templates)) {
+            $this->logError("configuration option '{$option}' invalid");
+            return false;
+        }
+
+        // server param values to check
+        $serverParamString = $this->request->getServer()->get($serverParamName);
+        if ($serverParamString === false) {
+            // check fails if server param is missing
+            return false;
+        }
+        $serverParams = $this->splitString(
+            $serverParamString, $this->serverParamDelimiter, $this->serverParamEscape
+        );
+
+        $result = false;
+        // check for each server param ...
+        foreach ($serverParams as $serverParam) {
+            // ... if it matches one of the templates (OR)
+            foreach ($templates as $template) {
+                if ($modifierMatch) {
+                    $result |= preg_match('/' . $template . '/', $serverParam);
+                } else {
+                    $result |= ($template === $serverParam);
+                }
+            }
+        }
+        if ($modifierNot) {
+            $result = !$result;
+        }
+
+        return $result;
+    }
+
+    /**
+     * Split string on delimiter unless dequalified with escape
+     *
+     * @param string $string    String to split
+     * @param string $delimiter Delimiter character
+     * @param string $escape    Escape character
+     *
+     * @return array split string parts
+     */
+    protected function splitString($string, $delimiter, $escape)
+    {
+        if ($delimiter === '') {
+            return [$string];
+        }
+
+        if ($delimiter === ' ') {
+            $pattern = ' +';
+        } else {
+            $pattern = preg_quote($delimiter, '/');
+        }
+
+        if ($escape === '') {
+            $pattern = '(?<!' . preg_quote($escape, '/') . ')' . $pattern;
+        }
+
+        return str_replace(
+            $escape . $delimiter, $delimiter,
+            preg_split('/' . $pattern . '/', $string)
+        );
+    }
+}
diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Role/PermissionProvider/ServerParamTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Role/PermissionProvider/ServerParamTest.php
new file mode 100644
index 00000000000..84b59dbe42c
--- /dev/null
+++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Role/PermissionProvider/ServerParamTest.php
@@ -0,0 +1,229 @@
+<?php
+/**
+ * PermissionProvider ServerParam 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Tests
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @author   Bernd Oberknapp <bo@ub.uni-freiburg.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:unit_tests Wiki
+ */
+namespace VuFindTest\Role\PermissionProvider;
+use VuFind\Role\PermissionProvider\ServerParam;
+
+/**
+ * PermissionProvider ServerParam Test Class
+ *
+ * @category VuFind2
+ * @package  Tests
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @author   Bernd Oberknapp <bo@ub.uni-freiburg.de>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:unit_tests Wiki
+ */
+class ServerParamTest extends \VuFindTest\Unit\TestCase
+{
+    /**
+     * Test single option with matching string
+     *
+     * @return void
+     */
+    public function testStringTrue()
+    {
+        $this->checkServerParams(
+            ['testheader' => 'testvalue'],
+            'testheader testvalue',
+            ['loggedin']
+        );
+    }
+
+    /**
+     * Test option array with matching string
+     *
+     * @return void
+     */
+    public function testArrayTrue()
+    {
+        $this->checkServerParams(
+            ['testheader' => 'testvalue'],
+            ['testheader testvalue'],
+            ['loggedin']
+        );
+    }
+
+    /**
+     * Test multiple options with matching headers
+     *
+     * @return void
+     */
+    public function testOptionsAndTrue()
+    {
+        $this->checkServerParams(
+            ['testheader1' => 'testvalue1', 'testheader2' => 'testvalue2'],
+            ['testheader1 testvalue1', 'testheader2 testvalue2'],
+            ['loggedin']
+        );
+    }
+
+    /**
+     * Test multiple options with no matching header
+     *
+     * @return void
+     */
+    public function testOptionsAndFalse()
+    {
+        $this->checkServerParams(
+            ['testheader1' => 'testvalue1'],
+            ['testheader1 testvalue1', 'testheader2 testvalue2'],
+            []
+        );
+    }
+
+    /**
+     * Test option with multiple values and matching header
+     *
+     * @return void
+     */
+    public function testOptionValuesOrTrue()
+    {
+        $this->checkServerParams(
+            ['testheader' => 'testvalue1'],
+            ['testheader testvalue1 testvalue2'],
+            ['loggedin']
+        );
+    }
+
+    /**
+     * Test option with multiple values and no matching header
+     *
+     * @return void
+     */
+    public function testOptionValuesOrFalse()
+    {
+        $this->checkServerParams(
+            ['testheader' => 'testvalue'],
+            ['testheader testvalue1 testvalue2'],
+            []
+        );
+    }
+
+    /**
+     * Test option with regex modifier and matching header
+     *
+     * @return void
+     */
+    public function testOptionRegexTrue()
+    {
+        $this->checkServerParams(
+            ['testheader' => 'testvalue'],
+            ['testheader ~ ^testvalue$'],
+            ['loggedin']
+        );
+    }
+
+    /**
+     * Test option with regex modifier and no matching header
+     *
+     * @return void
+     */
+    public function testOptionRegexFalse()
+    {
+        $this->checkServerParams(
+            ['testheader' => 'testvalue'],
+            ['testheader ~ ^estvalue'],
+            []
+        );
+    }
+
+    /**
+     * Test option with not modifier and matching header
+     *
+     * @return void
+     */
+    public function testOptionNotTrue()
+    {
+        $this->checkServerParams(
+            ['testheader' => 'testvalue'],
+            ['testheader ! testval'],
+            ['loggedin']
+        );
+    }
+
+    /**
+     * Test option with not modifier and no matching header
+     *
+     * @return void
+     */
+    public function testOptionNotFalse()
+    {
+        $this->checkServerParams(
+            ['testheader' => 'testvalue'],
+            ['testheader ! testvalue'],
+            []
+        );
+    }
+
+    /**
+     * Test option with not regex modifier and matching header
+     *
+     * @return void
+     */
+    public function testOptionNotRegexTrue()
+    {
+        $this->checkServerParams(
+            ['testheader' => 'testvalue'],
+            ['testheader !~ testval$'],
+            ['loggedin']
+        );
+    }
+
+    /**
+     * Test option with not regex modifier and no matching header
+     *
+     * @return void
+     */
+    public function testOptionNotRegexFalse()
+    {
+        $this->checkServerParams(
+            ['testheader' => 'testvalue'],
+            ['testheader !~ ^testvalue'],
+            []
+        );
+    }
+
+    /**
+     * Setup request and header objects, run getPermissions and check the result
+     *
+     * @param array $headers        Request headers
+     * @param mixed $options        options as from configuration
+     * @param array $expectedResult expected result returned by getPermissions
+     *
+     * @return void
+     */
+    protected function checkServerParams($headers, $options, $expectedResult)
+    {
+        $request = new \Zend\Http\PhpEnvironment\Request();
+        $request->setServer(new \Zend\Stdlib\Parameters($headers));
+        $header = new ServerParam($request);
+        $result = $header->getPermissions($options);
+        $this->assertEquals($result, $expectedResult);
+    }
+}
-- 
GitLab