diff --git a/config/vufind/EDS.ini b/config/vufind/EDS.ini index 72849336d86..cc674a77c17 100644 --- a/config/vufind/EDS.ini +++ b/config/vufind/EDS.ini @@ -443,3 +443,18 @@ DetailPageFormat = 'Long' ; than you define in ShortAuthorLimit, then VuFind will display the more_authors_abbrev text. ; If undefined, the default value is 3. ;ShortAuthorLimit = 3 + +[AdditionalHeaders] +; Due to the nature of EDS API integrations EBSCO's web application firewall (WAF) does not have sufficient +; data to distinguish bots from users. By sending some end-user data to EDS API, EBSCO's WAF can make more +; informed decisions regarding bots. +; This is important to guarantee accurate COUNTER 5 usage reports. The Counter Code of Practice 5.1 stipulates: +; "Activity generated by internet robots and crawlers MUST be excluded from all COUNTER usage reports." +; See https://cop5.countermetrics.org/en/5.1/07-processing/08-internet-robots-and-crawlers.html + +; If you are already protecting your site from bots, e.g. through your own WAF, you might not wish to enable this + +; Please note that this will send the end-user's IP Address & User Agent to EBSCO as x-eis-enduser-ip-address +; and x-eis-enduser-user-agent headers, so you might thus wish to consult your privacy officer. + +; send_user_ip = true diff --git a/module/VuFind/src/VuFind/Search/Factory/EdsBackendFactory.php b/module/VuFind/src/VuFind/Search/Factory/EdsBackendFactory.php index a26d6a08901..01332441cc4 100644 --- a/module/VuFind/src/VuFind/Search/Factory/EdsBackendFactory.php +++ b/module/VuFind/src/VuFind/Search/Factory/EdsBackendFactory.php @@ -185,6 +185,7 @@ protected function createConnectorOptions() 'api_url' => $this->edsConfig->General->api_url ?? $this->defaultApiUrl, 'is_guest' => !$auth->isGranted('access.EDSExtendedResults'), + 'send_user_ip' => $this->edsConfig->AdditionalHeaders->send_user_ip ?? false, ]; if (isset($this->edsConfig->General->auth_url)) { $options['auth_url'] = $this->edsConfig->General->auth_url; @@ -198,6 +199,14 @@ protected function createConnectorOptions() if (!empty($this->edsConfig->EBSCO_Account->api_key_guest)) { $options['api_key_guest'] = $this->edsConfig->EBSCO_Account->api_key_guest; } + if ($options['send_user_ip']) { + $options['ip_to_report'] = $this->getService(\VuFind\Net\UserIpReader::class)->getUserIp(); + $options['report_vendor_version'] = \VuFind\Config\Version::getBuildVersion(); + $server = $this->getService(\VuFind\Http\PhpEnvironment\Request::class)->getServer(); + if (!empty($server['HTTP_USER_AGENT'])) { + $options['user_agent'] = $server['HTTP_USER_AGENT']; + } + } return $options; } diff --git a/module/VuFindSearch/src/VuFindSearch/Backend/EDS/Base.php b/module/VuFindSearch/src/VuFindSearch/Backend/EDS/Base.php index 7e886631141..92252dc322a 100644 --- a/module/VuFindSearch/src/VuFindSearch/Backend/EDS/Base.php +++ b/module/VuFindSearch/src/VuFindSearch/Backend/EDS/Base.php @@ -117,6 +117,34 @@ abstract class Base implements LoggerAwareInterface */ protected $isGuest = true; + /** + * Indicator if additional headers should be sent + * + * @var bool + */ + protected $sendUserIp = false; + + /** + * Vendor (e.g. 10.1) + * + * @var ?string + */ + protected $reportVendorVersion = null; + + /** + * IpToReport (e.g. 123.123.123.13) + * + * @var ?string + */ + protected $ipToReport = null; + + /** + * UserAgent (e.g. 10.1) + * + * @var ?string + */ + protected $userAgent = null; + /** * Constructor * @@ -158,6 +186,18 @@ public function __construct($settings = []) case 'is_guest': $this->isGuest = $value; break; + case 'send_user_ip': + $this->sendUserIp = $value; + break; + case 'report_vendor_version': + $this->reportVendorVersion = $value; + break; + case 'ip_to_report': + $this->ipToReport = $value; + break; + case 'user_agent': + $this->userAgent = $value; + break; } } } @@ -366,7 +406,7 @@ public function autocomplete($query, $type, $data, $raw = false) $url = $data['url'] . '?' . http_build_query($params); $this->debug('Autocomplete URL: ' . $url); - $response = $this->call($url, null, null, 'GET', null); + $response = $this->call($url, [], null, 'GET', null); return $raw ? $response : $this->parseAutocomplete($response); } @@ -405,7 +445,7 @@ public function authenticate( $authInfo['Options'] = $params; } $messageBody = json_encode($authInfo); - return $this->call($url, null, null, 'POST', $messageBody, '', false); + return $this->call($url, [], null, 'POST', $messageBody, '', false); } /** @@ -461,7 +501,7 @@ protected function createQSFromArray($params) */ protected function call( $baseUrl, - $headerParams, + $headerParams = [], $params = [], $method = 'GET', $message = null, @@ -473,27 +513,13 @@ protected function call( $queryString = implode('&', $queryParameters); $this->debug("Querystring to use: $queryString "); // Build headers - $headers = [ - 'Accept' => $this->accept, - 'Content-Type' => $this->contentType, - 'Accept-Encoding' => 'gzip,deflate', - ]; + $headers = $this->getRequestHeaders($headerParams); + // Debug some info about Guest Access & API Keys used $this->debug( 'isGuest: ' . ($this->isGuest ? 'true' : 'false') . ' | APIKey: ' . ($this->apiKey ? substr($this->apiKey, 0, 10) : '-') . ' | APIKey Guest: ' . ($this->apiKeyGuest ? substr($this->apiKeyGuest, 0, 10) : '-') ); - if (null != $headerParams) { - foreach ($headerParams as $key => $value) { - $headers[$key] = $value; - } - } - if (!empty($this->apiKey)) { - $headers['x-api-key'] = $this->apiKey; - } - if ($this->isGuest && !empty($this->apiKeyGuest)) { - $headers['x-api-key'] = $this->apiKeyGuest; - } $response = $this->httpRequest( $baseUrl, $method, @@ -506,6 +532,41 @@ protected function call( return $this->process($response); } + /** + * Creat Header Array for Call Function + * + * @param array $headerParams An array (could be empty) of headers to build + * + * @return array Array of Headers to be used in call function + */ + protected function getRequestHeaders(array $headerParams = []): array + { + $headers = [ + 'Accept' => $this->accept, + 'Content-Type' => $this->contentType, + 'Accept-Encoding' => 'gzip,deflate', + ]; + foreach ($headerParams as $key => $value) { + $headers[$key] = $value; + } + if (!empty($this->apiKey)) { + $headers['x-api-key'] = $this->apiKey; + } + if ($this->isGuest && !empty($this->apiKeyGuest)) { + $headers['x-api-key'] = $this->apiKeyGuest; + } + if ($this->sendUserIp) { + $headers['x-eis-enduser-ip-address'] = $this->ipToReport ?? '-'; + $headers['x-eis-enduser-user-agent'] = $this->userAgent ?? 'No user agent'; + $headers['x-eis-vendor'] = 'VuFind'; + if (!empty($this->reportVendorVersion)) { + $headers['x-eis-vendor-version'] = $this->reportVendorVersion; + } + } + + return $headers; + } + /** * Process EDS API response message *