diff --git a/config/vufind/config.ini b/config/vufind/config.ini index fbfc1a2e11ef5dc59f6fbb0e858b5e4d85a2e9f1..d234f9906c95cff6c972d0c013fe26153d0082fb 100644 --- a/config/vufind/config.ini +++ b/config/vufind/config.ini @@ -1329,6 +1329,12 @@ url = https://www.myendnoteweb.com/EndNoteWeb.html ;type = socks5 ;type = socks5_hostname +; If VuFind is running behind a proxy that uses X-Real-IP/X-Forwarded-For headers, +; you should turn this setting on so that VuFind reports correct user IP +; addresses, and sets permissions appropriately. If you are NOT behind a proxy, you +; should leave this off to prevent spoofing. +allow_forwarded_ips = false + ; Default HTTP settings can be loaded here. These values will be passed to ; the \Zend\Http\Client's setOptions method. [Http] diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php index 643692d96d95b58c5275e62d62dfa8e1ed820404..f93e5666e989c2c568a5600901b3b5bb8bf7cdc4 100644 --- a/module/VuFind/config/module.config.php +++ b/module/VuFind/config/module.config.php @@ -397,6 +397,7 @@ $config = [ 'VuFind\Mailer\Mailer' => 'VuFind\Mailer\Factory', 'VuFind\MetadataVocabulary\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory', 'VuFind\Net\IpAddressUtils' => 'Laminas\ServiceManager\Factory\InvokableFactory', + 'VuFind\Net\UserIpReader' => 'VuFind\Net\UserIpReaderFactory', 'VuFind\OAI\Server' => 'VuFind\OAI\ServerFactory', 'VuFind\OAI\Server\Auth' => 'VuFind\OAI\ServerFactory', 'VuFind\QRCode\Loader' => 'VuFind\QRCode\LoaderFactory', diff --git a/module/VuFind/src/VuFind/Log/Logger.php b/module/VuFind/src/VuFind/Log/Logger.php index 1cd90f5f116d6ff8f6e3dbdd4e687e926be75e4a..599e6545ef1be77c55ae405945f5c59f275466b2 100644 --- a/module/VuFind/src/VuFind/Log/Logger.php +++ b/module/VuFind/src/VuFind/Log/Logger.php @@ -28,6 +28,8 @@ namespace VuFind\Log; use Laminas\Log\Logger as BaseLogger; +use Traversable; +use VuFind\Net\UserIpReader; /** * This class wraps the BaseLogger class to allow for log verbosity @@ -47,6 +49,32 @@ class Logger extends BaseLogger */ protected $debugNeeded = false; + /** + * User IP address reader + * + * @var UserIpReader + */ + protected $userIpReader; + + /** + * Constructor + * + * Set options for a logger. Accepted options are: + * - writers: array of writers to add to this logger + * - exceptionhandler: if true register this logger as exceptionhandler + * - errorhandler: if true register this logger as errorhandler + * - vufind_ip_reader: UserIpReader object to use for IP lookups + * + * @param array|Traversable $options Configuration options + * + * @throws \Laminas\Log\Exception\InvalidArgumentException + */ + public function __construct($options = null) + { + parent::__construct($options); + $this->userIpReader = $options['vufind_ip_reader'] ?? null; + } + /** * Is one of the log writers listening for debug messages? (This is useful to * know, since some code can save time that would be otherwise wasted generating @@ -132,9 +160,10 @@ class Logger extends BaseLogger $prev = $prev->getPrevious(); } $referer = $server->get('HTTP_REFERER', 'none'); - $ip = $server->get('HTTP_X_FORWARDED_FOR') ?? $server->get('REMOTE_ADDR'); + $ipAddr = $this->userIpReader !== null + ? $this->userIpReader->getUserIp() : $server->get('REMOTE_ADDR'); $basicServer - = '(Server: IP = ' . $ip . ', ' + = '(Server: IP = ' . $ipAddr . ', ' . 'Referer = ' . $referer . ', ' . 'User Agent = ' . $server->get('HTTP_USER_AGENT') . ', ' diff --git a/module/VuFind/src/VuFind/Log/LoggerFactory.php b/module/VuFind/src/VuFind/Log/LoggerFactory.php index 6cc2cc11a04e21f9839548dca3096ec070c79f2c..1a36d0e38d14fa7ab15eb1429fd1960d65eb4dd4 100644 --- a/module/VuFind/src/VuFind/Log/LoggerFactory.php +++ b/module/VuFind/src/VuFind/Log/LoggerFactory.php @@ -381,7 +381,11 @@ class LoggerFactory implements FactoryInterface $proxy->setProxyInitializer(null); // Now build the actual service: - $wrapped = new $requestedName(); + $loggerOptions = [ + 'vufind_ip_reader' => + $container->get(\VuFind\Net\UserIpReader::class) + ]; + $wrapped = new $requestedName($loggerOptions); $this->configureLogger($container, $wrapped); }; $cfg = $container->get(\ProxyManager\Configuration::class); diff --git a/module/VuFind/src/VuFind/Net/UserIpReader.php b/module/VuFind/src/VuFind/Net/UserIpReader.php new file mode 100644 index 0000000000000000000000000000000000000000..ae84e23b39eacc8f4f340a51387fa658276471a0 --- /dev/null +++ b/module/VuFind/src/VuFind/Net/UserIpReader.php @@ -0,0 +1,94 @@ +<?php +/** + * Service to retrieve user IP address. + * + * PHP version 7 + * + * Copyright (C) Villanova University 2020. + * + * 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 Net + * @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\Net; + +use Laminas\Stdlib\Parameters; + +/** + * Service to retrieve user IP address. + * + * @category VuFind + * @package Net + * @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 UserIpReader +{ + /** + * Server parameters + * + * @var Parameters + */ + protected $server; + + /** + * Should we respect the X-Forwarded-For header? + * + * @var bool + */ + protected $allowForwardedIps; + + /** + * Constructor + * + * @param Parameters $server Server parameters + * @param bool $allowForwardedIps Should we respect the X-Forwarded-For + * header? + */ + public function __construct(Parameters $server, $allowForwardedIps = false) + { + $this->server = $server; + $this->allowForwardedIps = $allowForwardedIps; + } + + /** + * Get the active user's IP address. Returns null if no address can be found. + * + * @return string + */ + public function getUserIp() + { + if ($this->allowForwardedIps) { + // First check X-Real-IP; this is most accurate when set... + $realIp = $this->server->get('HTTP_X_REAL_IP'); + if (!empty($realIp)) { + return $realIp; + } + // Next, try X-Forwarded-For; if it's a comma-separated list, use + // only the first part. + $forwarded = $this->server->get('HTTP_X_FORWARDED_FOR'); + if (!empty($forwarded)) { + $parts = explode(',', $forwarded); + return trim($parts[0]); + } + } + // Default case: use REMOTE_ADDR directly. + return $this->server->get('REMOTE_ADDR'); + } +} diff --git a/module/VuFind/src/VuFind/Net/UserIpReaderFactory.php b/module/VuFind/src/VuFind/Net/UserIpReaderFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..02dc9bc3564a8f57e82f9280a008c1764d4e3c35 --- /dev/null +++ b/module/VuFind/src/VuFind/Net/UserIpReaderFactory.php @@ -0,0 +1,73 @@ +<?php +/** + * Factory for instantiating UserIpReader. + * + * PHP version 7 + * + * Copyright (C) Villanova University 2020. + * + * 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 Net + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +namespace VuFind\Net; + +use Interop\Container\ContainerInterface; + +/** + * Factory for instantiating UserIpReader. + * + * @category VuFind + * @package Net + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +class UserIpReaderFactory implements \Laminas\ServiceManager\Factory\FactoryInterface +{ + /** + * Create an object + * + * @param ContainerInterface $container Service manager + * @param string $requestedName Service being created + * @param null|array $options Extra options (optional) + * + * @return object + * + * @throws ServiceNotFoundException if unable to resolve the service. + * @throws ServiceNotCreatedException if an exception is raised when + * creating a service. + * @throws ContainerException if any other error occurs + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function __invoke(ContainerInterface $container, $requestedName, + array $options = null + ) { + if (!empty($options)) { + throw new \Exception('Unexpected options passed to factory.'); + } + $config = $container->get(\VuFind\Config\PluginManager::class) + ->get('config'); + $allowForwardedIps = $config->Proxy->allow_forwarded_ips ?? false; + return new $requestedName( + $container->get('Request')->getServer(), + $allowForwardedIps + ); + } +} diff --git a/module/VuFind/src/VuFind/Resolver/Driver/Ezb.php b/module/VuFind/src/VuFind/Resolver/Driver/Ezb.php index 9dcbe6b3be392dbc20104699416479dc6f8f4333..5e5eadcd93b5f5daedd656067bd3b0e514b67e58 100644 --- a/module/VuFind/src/VuFind/Resolver/Driver/Ezb.php +++ b/module/VuFind/src/VuFind/Resolver/Driver/Ezb.php @@ -38,6 +38,7 @@ namespace VuFind\Resolver\Driver; use DOMDocument; use DOMXpath; +use VuFind\Net\UserIpReader; /** * EZB Link Resolver Driver @@ -58,16 +59,26 @@ class Ezb extends AbstractBase */ protected $httpClient; + /** + * User IP address reader + * + * @var UserIpReader + */ + protected $userIpReader; + /** * Constructor * - * @param string $baseUrl Base URL for link resolver - * @param \Laminas\Http\Client $httpClient HTTP client + * @param string $baseUrl Base URL for link resolver + * @param \Laminas\Http\Client $httpClient HTTP client + * @param UserIpReader $userIpReader User IP address reader */ - public function __construct($baseUrl, \Laminas\Http\Client $httpClient) - { + public function __construct($baseUrl, \Laminas\Http\Client $httpClient, + UserIpReader $userIpReader = null + ) { parent::__construct($baseUrl); $this->httpClient = $httpClient; + $this->userIpReader = $userIpReader; } /** @@ -146,17 +157,20 @@ class Ezb extends AbstractBase foreach ($tmp as $current) { $tmp2 = explode('=', $current, 2); - $parsed[$tmp2[0]] = $tmp2[1]; + $parsed[$tmp2[0]] = $tmp2[1] ?? null; } // Downgrade 1.0 to 0.1 - if ($parsed['ctx_ver'] == 'Z39.88-2004') { + if ($parsed['ctx_ver'] ?? null == 'Z39.88-2004') { $openURL = $this->downgradeOpenUrl($parsed); } // make the request IP-based to allow automatic // indication on institution level - $openURL .= '&pid=client_ip%3D' . $_SERVER['REMOTE_ADDR']; + $ipAddr = $this->userIpReader !== null + ? $this->userIpReader->getUserIp() + : $_SERVER['REMOTE_ADDR']; + $openURL .= '&pid=client_ip%3D' . urlencode($ipAddr); // Make the call to the EZB and load results $url = $this->baseUrl . '?' . $openURL; diff --git a/module/VuFind/src/VuFind/Resolver/Driver/EzbFactory.php b/module/VuFind/src/VuFind/Resolver/Driver/EzbFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..4c34431c82e493905c8e6d192ee8a1e228af5fe1 --- /dev/null +++ b/module/VuFind/src/VuFind/Resolver/Driver/EzbFactory.php @@ -0,0 +1,63 @@ +<?php +/** + * Factory for EZB resolver driver. + * + * PHP version 7 + * + * Copyright (C) Villanova University 2020. + * + * 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 Resolver_Drivers + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +namespace VuFind\Resolver\Driver; + +use Interop\Container\ContainerInterface; + +/** + * Factory for EZB resolver driver. + * + * @category VuFind + * @package Resolver_Drivers + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +class EzbFactory extends DriverWithHttpClientFactory +{ + /** + * Create an object + * + * @param ContainerInterface $container Service manager + * @param string $requestedName Service being created + * @param null|array $options Extra options (optional) + * + * @return object + * + * @throws ServiceNotFoundException if unable to resolve the service. + * @throws ServiceNotCreatedException if an exception is raised when + * creating a service. + * @throws ContainerException if any other error occurs + */ + public function __invoke(ContainerInterface $container, $requestedName, + array $options = null + ) { + $options = [$container->get(\VuFind\Net\UserIpReader::class)]; + return parent::__invoke($container, $requestedName, $options); + } +} diff --git a/module/VuFind/src/VuFind/Resolver/Driver/PluginManager.php b/module/VuFind/src/VuFind/Resolver/Driver/PluginManager.php index eb88efd26e933a546a7b4fec1524a0e75a14ade1..08a677c92001df0992ecb6f450f4114ca1afea73 100644 --- a/module/VuFind/src/VuFind/Resolver/Driver/PluginManager.php +++ b/module/VuFind/src/VuFind/Resolver/Driver/PluginManager.php @@ -66,7 +66,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager Alma::class => DriverWithHttpClientFactory::class, Threesixtylink::class => DriverWithHttpClientFactory::class, Demo::class => InvokableFactory::class, - Ezb::class => DriverWithHttpClientFactory::class, + Ezb::class => EzbFactory::class, Sfx::class => DriverWithHttpClientFactory::class, Redi::class => DriverWithHttpClientFactory::class, Generic::class => AbstractBaseFactory::class, diff --git a/module/VuFind/src/VuFind/Role/PermissionProvider/IpRange.php b/module/VuFind/src/VuFind/Role/PermissionProvider/IpRange.php index 5cd0094c12621a184c6928b38a7c16f57877a2f9..8791ef2ed3855905f12d08709292b7a51b7abe1c 100644 --- a/module/VuFind/src/VuFind/Role/PermissionProvider/IpRange.php +++ b/module/VuFind/src/VuFind/Role/PermissionProvider/IpRange.php @@ -32,6 +32,7 @@ namespace VuFind\Role\PermissionProvider; use Laminas\Stdlib\RequestInterface; use VuFind\Net\IpAddressUtils; +use VuFind\Net\UserIpReader; /** * IpRange permission provider for VuFind. @@ -60,16 +61,26 @@ class IpRange implements PermissionProviderInterface */ protected $ipAddressUtils; + /** + * User IP address reader + * + * @var UserIpReader + */ + protected $userIpReader; + /** * Constructor * - * @param RequestInterface $request Request object - * @param IpAddressUtils $ipUtils IpAddressUtils object + * @param RequestInterface $request Request object + * @param IpAddressUtils $ipUtils IpAddressUtils object + * @param UserIpReader $userIpReader User IP address reader */ - public function __construct(RequestInterface $request, IpAddressUtils $ipUtils) - { + public function __construct(RequestInterface $request, IpAddressUtils $ipUtils, + UserIpReader $userIpReader = null + ) { $this->request = $request; $this->ipAddressUtils = $ipUtils; + $this->userIpReader = $userIpReader; } /** @@ -82,13 +93,15 @@ class IpRange implements PermissionProviderInterface */ public function getPermissions($options) { - if (PHP_SAPI == 'cli') { - return []; - } // Check if any regex matches.... - $ip = $this->request->getServer()->get('HTTP_X_FORWARDED_FOR') - ?? $this->request->getServer()->get('REMOTE_ADDR'); - if ($this->ipAddressUtils->isInRange($ip, (array)$options)) { + if ($this->userIpReader !== null) { + $ipAddr = $this->userIpReader->getUserIp(); + } elseif (PHP_SAPI == 'cli') { + $ipAddr = null; + } else { + $ipAddr = $this->request->getServer()->get('REMOTE_ADDR'); + } + if ($this->ipAddressUtils->isInRange($ipAddr, (array)$options)) { // Match? Grant to all users (guest or logged in). return ['guest', 'loggedin']; } diff --git a/module/VuFind/src/VuFind/Role/PermissionProvider/IpRangeFactory.php b/module/VuFind/src/VuFind/Role/PermissionProvider/IpRangeFactory.php index d863110274d8ced7b4eaa3aa33bfac845b22063c..5f697fbb8ddde47c2128bb5683066dec38fde111 100644 --- a/module/VuFind/src/VuFind/Role/PermissionProvider/IpRangeFactory.php +++ b/module/VuFind/src/VuFind/Role/PermissionProvider/IpRangeFactory.php @@ -64,7 +64,8 @@ class IpRangeFactory implements \Laminas\ServiceManager\Factory\FactoryInterface } return new $requestedName( $container->get('Request'), - $container->get(\VuFind\Net\IpAddressUtils::class) + $container->get(\VuFind\Net\IpAddressUtils::class), + $container->get(\VuFind\Net\UserIpReader::class) ); } } diff --git a/module/VuFind/src/VuFind/Role/PermissionProvider/IpRegEx.php b/module/VuFind/src/VuFind/Role/PermissionProvider/IpRegEx.php index 70249137f228f37e07e7808ed7c9fc9de05274d6..d86f3bc0fdd937f7e74af3dc960a854831d7d3c9 100644 --- a/module/VuFind/src/VuFind/Role/PermissionProvider/IpRegEx.php +++ b/module/VuFind/src/VuFind/Role/PermissionProvider/IpRegEx.php @@ -28,6 +28,7 @@ namespace VuFind\Role\PermissionProvider; use Laminas\Http\PhpEnvironment\Request; +use VuFind\Net\UserIpReader; /** * IpRegEx permission provider for VuFind. @@ -47,14 +48,23 @@ class IpRegEx implements PermissionProviderInterface */ protected $request; + /** + * User IP address reader + * + * @var UserIpReader + */ + protected $userIpReader; + /** * Constructor * - * @param Request $request Request object + * @param Request $request Request object + * @param UserIpReader $userIpReader User IP address reader */ - public function __construct(Request $request) + public function __construct(Request $request, UserIpReader $userIpReader = null) { $this->request = $request; + $this->userIpReader = $userIpReader; } /** @@ -68,10 +78,15 @@ class IpRegEx implements PermissionProviderInterface public function getPermissions($options) { // Check if any regex matches.... - $ip = $this->request->getServer()->get('HTTP_X_FORWARDED_FOR') - ?? $this->request->getServer()->get('REMOTE_ADDR'); + if ($this->userIpReader !== null) { + $ipAddr = $this->userIpReader->getUserIp(); + } elseif (PHP_SAPI == 'cli') { + $ipAddr = null; + } else { + $ipAddr = $this->request->getServer()->get('REMOTE_ADDR'); + } foreach ((array)$options as $current) { - if (preg_match($current, $ip)) { + if (preg_match($current, $ipAddr)) { // Match? Grant to all users (guest or logged in). return ['guest', 'loggedin']; } diff --git a/module/VuFind/src/VuFind/Role/PermissionProvider/IpRegExFactory.php b/module/VuFind/src/VuFind/Role/PermissionProvider/IpRegExFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..545d6b91d3cba6b330131bbd616a572985e794c8 --- /dev/null +++ b/module/VuFind/src/VuFind/Role/PermissionProvider/IpRegExFactory.php @@ -0,0 +1,70 @@ +<?php +/** + * Factory for instantiating IpRegEx permission provider. + * + * PHP version 7 + * + * Copyright (C) Villanova University 2020. + * + * 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> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +namespace VuFind\Role\PermissionProvider; + +use Interop\Container\ContainerInterface; + +/** + * Factory for instantiating IpRegEx permission provider. + * + * @category VuFind + * @package Authorization + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +class IpRegExFactory implements \Laminas\ServiceManager\Factory\FactoryInterface +{ + /** + * Create an object + * + * @param ContainerInterface $container Service manager + * @param string $requestedName Service being created + * @param null|array $options Extra options (optional) + * + * @return object + * + * @throws ServiceNotFoundException if unable to resolve the service. + * @throws ServiceNotCreatedException if an exception is raised when + * creating a service. + * @throws ContainerException if any other error occurs + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function __invoke(ContainerInterface $container, $requestedName, + array $options = null + ) { + if (!empty($options)) { + throw new \Exception('Unexpected options passed to factory.'); + } + return new $requestedName( + $container->get('Request'), + $container->get(\VuFind\Net\UserIpReader::class) + ); + } +} diff --git a/module/VuFind/src/VuFind/Role/PermissionProvider/PluginManager.php b/module/VuFind/src/VuFind/Role/PermissionProvider/PluginManager.php index 35786501578162ae5b4e847cf4e35f477feed0b6..fd89480367cbcd645eeba62ecc0b402273726793 100644 --- a/module/VuFind/src/VuFind/Role/PermissionProvider/PluginManager.php +++ b/module/VuFind/src/VuFind/Role/PermissionProvider/PluginManager.php @@ -60,7 +60,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager */ protected $factories = [ IpRange::class => IpRangeFactory::class, - IpRegEx::class => InjectRequestFactory::class, + IpRegEx::class => IpRegExFactory::class, Role::class => \Laminas\ServiceManager\Factory\InvokableFactory::class, ServerParam::class => InjectRequestFactory::class, Shibboleth::class => ShibbolethFactory::class, diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Net/UserIpReaderTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Net/UserIpReaderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ff0ecb5a818033634907e48f783cd6890812c117 --- /dev/null +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Net/UserIpReaderTest.php @@ -0,0 +1,130 @@ +<?php +/** + * UserIpReader Test Class + * + * PHP version 7 + * + * Copyright (C) Villanova University 2020. + * + * 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> + * @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\Net; + +use Laminas\Stdlib\Parameters; +use VuFind\Net\UserIpReader; + +/** + * UserIpReader Test Class + * + * @category VuFind + * @package Tests + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +class UserIpReaderTest extends \VuFindTest\Unit\TestCase +{ + /** + * Test X-Real-IP; it should take priority over all other settings when + * forwarding is allowed. + * + * @return void + */ + public function testXRealIp() + { + $params = new Parameters( + [ + 'HTTP_X_REAL_IP' => '1.2.3.4', + 'HTTP_X_FORWARDED_FOR' => '5.6.7.8', + 'REMOTE_ADDR' => '127.0.0.1', + ] + ); + // Test appropriate behavior with forwarding enabled: + $reader1 = new UserIpReader($params, true); + $this->assertEquals('1.2.3.4', $reader1->getUserIp()); + // Test appropriate behavior with forwarding disabled: + $reader2 = new UserIpReader($params, false); + $this->assertEquals('127.0.0.1', $reader2->getUserIp()); + } + + /** + * Test X-Forwarded-For (single value); it should take priority over REMOTE_ADDR + * when forwarding is allowed. + * + * @return void + */ + public function testXForwardedForSingle() + { + $params = new Parameters( + [ + 'HTTP_X_FORWARDED_FOR' => '5.6.7.8', + 'REMOTE_ADDR' => '127.0.0.1', + ] + ); + // Test appropriate behavior with forwarding enabled: + $reader1 = new UserIpReader($params, true); + $this->assertEquals('5.6.7.8', $reader1->getUserIp()); + // Test appropriate behavior with forwarding disabled: + $reader2 = new UserIpReader($params, false); + $this->assertEquals('127.0.0.1', $reader2->getUserIp()); + } + + /** + * Test X-Forwarded-For (multi-value); the leftmost IP should take priority over + * REMOTE_ADDR when forwarding is allowed. + * + * @return void + */ + public function testXForwardedForMultiValued() + { + $params = new Parameters( + [ + 'HTTP_X_FORWARDED_FOR' => '5.6.7.8, 9.10.11.12', + 'REMOTE_ADDR' => '127.0.0.1', + ] + ); + // Test appropriate behavior with forwarding enabled: + $reader1 = new UserIpReader($params, true); + $this->assertEquals('5.6.7.8', $reader1->getUserIp()); + // Test appropriate behavior with forwarding disabled: + $reader2 = new UserIpReader($params, false); + $this->assertEquals('127.0.0.1', $reader2->getUserIp()); + } + + /** + * Test what happens when only REMOTE_ADDR is provided. + * + * @return void + */ + public function testXForwardedForSimple() + { + $params = new Parameters( + [ + 'REMOTE_ADDR' => '127.0.0.1', + ] + ); + // Test appropriate behavior with forwarding enabled: + $reader1 = new UserIpReader($params, true); + $this->assertEquals('127.0.0.1', $reader1->getUserIp()); + // Test appropriate behavior with forwarding disabled: + $reader2 = new UserIpReader($params, false); + $this->assertEquals('127.0.0.1', $reader2->getUserIp()); + } +} diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Resolver/Driver/EzbTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Resolver/Driver/EzbTest.php index 79a0fe13646fba014e0a6a4260ff7ec450dcac64..5bcccd54feb3e58d6ce1dc20d9c43f70a221e4ac 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/Resolver/Driver/EzbTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Resolver/Driver/EzbTest.php @@ -67,7 +67,7 @@ class EzbTest extends \VuFindTest\Unit\TestCase ]; /** - * Test + * Test link parsing * * @return void */ @@ -132,16 +132,34 @@ class EzbTest extends \VuFindTest\Unit\TestCase $this->assertEquals($result, $testResult); } + /** + * Test URL generation + * + * @return void + */ + public function testGetResolverUrl() + { + $ipAddr = '1.2.3.4'; + $connector = $this->createConnector(null, $ipAddr); + $expected = 'http://services.d-nb.de/fize-service/gvr/full.xml?' + . 'foo=bar&pid=client_ip%3D' . $ipAddr; + $this->assertEquals( + $expected, + $connector->getResolverUrl('foo=bar') + ); + } + /** * Create connector with fixture file. * * @param string $fixture Fixture file + * @param string $ipAddr Source IP address to simulate * * @return Connector * * @throws InvalidArgumentException Fixture file does not exist */ - protected function createConnector($fixture = null) + protected function createConnector($fixture = null, $ipAddr = '127.0.0.1') { $adapter = new TestAdapter(); if ($fixture) { @@ -158,12 +176,15 @@ class EzbTest extends \VuFindTest\Unit\TestCase $responseObj = HttpResponse::fromString($response); $adapter->setResponse($responseObj); } - $_SERVER['REMOTE_ADDR'] = "127.0.0.1"; - $client = new \Laminas\Http\Client(); $client->setAdapter($adapter); - $conn = new Ezb($this->openUrlConfig['OpenURL']['url'], $client); + $ipReader = $this->getMockBuilder(\VuFind\Net\UserIpReader::class) + ->disableOriginalConstructor() + ->getMock(); + $ipReader->expects($this->once())->method('getUserIp') + ->will($this->returnValue($ipAddr)); + $conn = new Ezb($this->openUrlConfig['OpenURL']['url'], $client, $ipReader); return $conn; } } diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Role/PermissionProvider/IpRangeTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Role/PermissionProvider/IpRangeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a4cb6d1a652d7f00a9e743a5f2c1c7636c3aa513 --- /dev/null +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Role/PermissionProvider/IpRangeTest.php @@ -0,0 +1,114 @@ +<?php +/** + * IpRange ServerParam Test Class + * + * PHP version 7 + * + * Copyright (C) Villanova University 2020. + * + * 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> + * @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\PermissionProvider; + +use VuFind\Net\IpAddressUtils; +use VuFind\Role\PermissionProvider\IpRange; + +/** + * IpRange ServerParam Test Class + * + * @category VuFind + * @package Tests + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +class IpRangeTest extends \VuFindTest\Unit\TestCase +{ + /** + * Get a permission provider with the specified IP assigned. + * + * @param string $ipAddr IP address to send to provider. + * @param IpAddressUtils $utils IP address utils to use + * + * @return IpRegEx + */ + protected function getPermissionProvider($ipAddr, IpAddressUtils $utils) + { + $mockRequestClass = $this->getMockClass( + \Laminas\Http\PhpEnvironment\Request::class + ); + $mockIpReader = $this->getMockBuilder(\VuFind\Net\UserIpReader::class) + ->disableOriginalConstructor() + ->getMock(); + $mockIpReader->expects($this->once())->method('getUserIp') + ->will($this->returnValue($ipAddr)); + return new IpRange(new $mockRequestClass, $utils, $mockIpReader); + } + + /** + * Test a matching range. + * + * @return void + */ + public function testMatchingRange() + { + // In this example, we'll pass the IP address as the options to the provider. + // Note that we're not actually testing the range checking itself, because + // we're mocking out the IpAddressUtils; we're just confirming that the parts + // fit together correctly. + $ipAddr = '123.124.125.126'; + $utils = $this->getMockBuilder(IpAddressUtils::class) + ->disableOriginalConstructor() + ->getMock(); + $utils->expects($this->once())->method('isInRange') + ->with($this->equalTo($ipAddr), $this->equalTo([$ipAddr])) + ->will($this->returnValue(true)); + $provider = $this->getPermissionProvider($ipAddr, $utils); + $this->assertEquals( + ['guest', 'loggedin'], $provider->getPermissions($ipAddr) + ); + } + + /** + * Test an array of non-matching ranges. + * + * @return void + */ + public function testNonMatchingRegExArray() + { + // In this example, we'll pass the IP address as the options to the provider. + // Note that we're not actually testing the range checking itself, because + // we're mocking out the IpAddressUtils; we're just confirming that the parts + // fit together correctly. + $ipAddr = '123.124.125.126'; + $options = [ + '1.2.3.4-1.2.3.7', + '2.3.4.5', + ]; + $utils = $this->getMockBuilder(IpAddressUtils::class) + ->disableOriginalConstructor() + ->getMock(); + $utils->expects($this->once())->method('isInRange') + ->with($this->equalTo($ipAddr), $this->equalTo($options)) + ->will($this->returnValue(false)); + $provider = $this->getPermissionProvider($ipAddr, $utils); + $this->assertEquals([], $provider->getPermissions($options)); + } +} diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Role/PermissionProvider/IpRegExTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Role/PermissionProvider/IpRegExTest.php new file mode 100644 index 0000000000000000000000000000000000000000..eeb00f81df77f7d674a1fd187035f152b26f29db --- /dev/null +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Role/PermissionProvider/IpRegExTest.php @@ -0,0 +1,91 @@ +<?php +/** + * IpRegEx ServerParam Test Class + * + * PHP version 7 + * + * Copyright (C) Villanova University 2020. + * + * 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> + * @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\PermissionProvider; + +use VuFind\Role\PermissionProvider\IpRegEx; + +/** + * IpRegEx ServerParam Test Class + * + * @category VuFind + * @package Tests + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +class IpRegExTest extends \VuFindTest\Unit\TestCase +{ + /** + * Get a permission provider with the specified IP assigned. + * + * @param string $ipAddr IP address to send to provider. + * + * @return IpRegEx + */ + protected function getPermissionProvider($ipAddr) + { + $mockRequestClass = $this->getMockClass( + \Laminas\Http\PhpEnvironment\Request::class + ); + $mockIpReader = $this->getMockBuilder(\VuFind\Net\UserIpReader::class) + ->disableOriginalConstructor() + ->getMock(); + $mockIpReader->expects($this->once())->method('getUserIp') + ->will($this->returnValue($ipAddr)); + return new IpRegEx(new $mockRequestClass, $mockIpReader); + } + + /** + * Test a matching regular expression. + * + * @return void + */ + public function testMatchingRegEx() + { + $regEx = '/123\.124\..*/'; + $provider = $this->getPermissionProvider('123.124.125.126'); + $this->assertEquals( + ['guest', 'loggedin'], $provider->getPermissions($regEx) + ); + } + + /** + * Test an array of non-matching regular expressions. + * + * @return void + */ + public function testNonMatchingRegExArray() + { + $regEx = [ + '/123\.124\..*/', + '/125\.126\..*/', + ]; + $provider = $this->getPermissionProvider('129.124.125.126'); + $this->assertEquals([], $provider->getPermissions($regEx)); + } +}