Skip to content
Snippets Groups Projects
Commit d04e26f3 authored by Michael Birkner's avatar Michael Birkner Committed by Demian Katz
Browse files

Completed work on Alma driver. (#955)

- Includes Alma-specific authentication module and controller for handling webhooks.
- Resolves VUFIND-1212.
parent abcf3e95
No related merge requests found
Showing
with 1802 additions and 138 deletions
......@@ -4,9 +4,11 @@ apiBaseUrl = "https://api-eu.hosted.exlibrisgroup.com/almaws/v1"
; An API key configured to allow access to Alma:
apiKey = "your-key-here"
[Holds]
; HMACKeys - A list of hold form element names that will be analyzed for consistency
; during hold form processing. Most users should not need to change this setting.
; For activating title level hold request, add "description" and "level".
HMACKeys = id:item_id:holding_id
; defaultRequiredDate - A colon-separated list used to set the default "not required
......@@ -25,3 +27,87 @@ extraHoldFields = comments:requiredByDate:pickUpLocation
; 2) "user-selected" to indicate that the user always has to choose the location
; 3) a value within the Location IDs returned by getPickUpLocations()
defaultPickUpLocation = ""
; The "NewUser" section defines some default values that are used when creating an account
; in Alma via its API. This is only relevant if you use the authentication method "AlmaDatabase"
; in the "Authentication" section of the "config.ini" file.
[NewUser]
; Mandatory. The Alma user record type. Usually "PUBLIC".
recordType = PUBLIC
; Mandatory. The Alma user account type. Usually this is "INTERNAL" if you use the AlmaDatabase
; authentication method.
accountType = INTERNAL
; Mandatory. The status of the Alma user account. Usually "ACTIVE".
status = ACTIVE
; Mandatory. The user group to which the new Alma account should belong. Use the code of one of
; the user groups that are defined in Alma (see "Alma Configuration -> User Management -> User Groups").
userGroup =
; Mandatory. The type of ID under which the username should be saved to Alma. Log in to the ExLibris developer
; network and check the Alma API documentation for possible values on this site:
; https://developers.exlibrisgroup.com/alma/apis/xsd/rest_user.xsd?tags=POST#user_identifier
idType =
; Mandatory. The preferred language of the new Alma account. This should normally be the Alma language
; code of your local language (see "Alma Configuration -> General -> Institution Languages").
preferredLanguage =
; Mandatory. The type of eMail of the users eMail address. Log in to the ExLibris developer network and
; check the Alma API documentation for possible values on this site:
; https://developers.exlibrisgroup.com/alma/apis/xsd/rest_user.xsd?tags=POST#email_types
emailType =
; Optional. Set the time period when the Alma account should expire. The given period will be added to the
; point in time of the Alma account creation. Use the DateInterval notation of PHP to express the period. See:
; https://secure.php.net/manual/en/dateinterval.construct.php#refsect1-dateinterval.construct-parameters
; If not set, 1 year (P1Y) will be used as default value.
expiryDate =
; Optional. Set the time period that should be used for the Alma user account purge date. The given period
; will be added to the point in time of the Alma account creation. Use the DateInterval notation of PHP to
; express the period. See:
; https://secure.php.net/manual/en/dateinterval.construct.php#refsect1-dateinterval.construct-parameters
; If not set, the purge date of the Alma user account will be empty.
purgeDate =
[FulfillmentUnits]
; Specify the association of fulfillment units and its locations. Take the codes from:
; Alma Configuration -> Fulfillment -> Fulfillment Units -> [Choose Fulfillment Unit] -> Fulfillment Unit Locations.
; Tip: Export the list from Alma as Excel and use the CONCATENATE() formula to generate this list.
; Format: FULFILLMENT_UNIT_CODE[] = LOCATION_CODE
; Example:
; STACKS[] = stack1
; STACKS[] = stack2
; STACKS[] = stack3
; LIMITED[] = periodicalroom
; LIMITED[] = musicrefs
; SHORTLOAN[] = office1
; SHORTLOAN[] = office2
[Requestable]
; Specify for which combination of user group and fulfillment unit (see above) the request link
; should be displayed (who is allowed to request what). Define every combination of fulfillment unit
; and user group and assign "N" for "No, not requestable for this user group" or "Y" for "Yes, is
; requestable for this user group". You will find the user group codes here:
; Alma Configuration -> User Management -> User Groups
; Format: FULFILLMENT_UNIT_CODE[USER_GROUP_CODE] = N
; Example:
; STACKS[STAFF] = Y
; STACKS[STUDENT] = Y
; STACKS[GUEST] = Y
; LIMITED[STAFF] = Y
; LIMITED[STUDENT] = N
; LIMITED[GUEST] = N
; SHORTLOAN[STAFF] = Y
; SHORTLOAN[STUDENT] = Y
; SHORTLOAN[GUEST] = N
[Webhook]
; The webhook secret. This must be the same value that was added to the Alma webhook configuration as a secret.
secret = YOUR_WEBHOOK_SECRET_FROM_ALMA
......@@ -328,13 +328,14 @@ title_level_holds_mode = "disabled"
; This section allows you to determine how the users will authenticate.
; You can use an LDAP directory, the local ILS (or multiple ILSes through
; the MultiILS option), the VuFind database (Database), Shibboleth, SIP2,
; CAS, Facebook or some combination of these (via the MultiAuth or ChoiceAuth
; options).
; the MultiILS option), the VuFind database (Database), AlmaDatabase (combination
; of VuFind database and Alma account), Shibboleth, SIP2, CAS, Facebook or some
; combination of these (via the MultiAuth or ChoiceAuth options).
[Authentication]
;method = LDAP
;method = ILS
method = Database
;method = AlmaDatabase
;method = Shibboleth
;method = SIP2
;method = CAS
......@@ -355,7 +356,7 @@ method = Database
hideLogin = false
; Set this to false if you would like to store local passwords in plain text
; (only applies when method = Database above).
; (only applies when method = Database or AlmaDatabase above).
hash_passwords = false
; Allow users to recover passwords via email (if supported by Auth method)
......@@ -387,7 +388,7 @@ ils_encryption_key = false
;ils_encryption_algo = "blowfish"
; This setting may optionally be uncommented to restrict the email domain(s) from
; which users are allowed to register when using the Database method.
; which users are allowed to register when using the Database or AlmaDatabase method.
;domain_whitelist[] = "myuniversity.edu"
;domain_whitelist[] = "mail.myuniversity.edu"
......@@ -406,8 +407,8 @@ ils_encryption_key = false
; Uncomment this line to switch on "privacy mode" in which no user information
; will be stored in the database. Note that this is incompatible with social
; features, password resets, and many other features. It is not recommended for
; use with "Database" authentication, since the user will be forced to create a
; new account upon every login.
; use with "Database" or "AlmaDatabase" authentication, since the user will be
; forced to create a new account upon every login.
;privacy = true
; Allow a user to delete their account. Default is false.
......
......@@ -166,3 +166,10 @@ role = loggedin
;require = ANY
;ipRange[] = '127.0.0.1'
;ipRange[] = '::1'
; Example permission for Alma webbooks
;[alma.Webhooks]
;permission[] = "access.alma.webhook.user"
;permission[] = "access.alma.webhook.challenge"
;require = ALL
;ipRange[] = "127.0.0.1"
......@@ -509,6 +509,7 @@ ill_request_processed = "Bearbeitet"
ill_request_profile_html = "Um die Fernleihe zu nutzen, richten Sie bitte ihr <a href="%%url%%">Bibliothekskatalog-Profil</a> ein."
ill_request_submit_text = "Anfrage abschicken"
Illustrated = "Abbildungen"
ils_account_create_error = "Es konnte kein Konto in unserem Bibliotheksverwaltungssystem für Sie erstellt werden. Wir entschuldigen uns für die Umstände und stehen für weitere Fragen gerne zur Verfügung."
ils_action_unavailable = "Diese Funktion ist für das aktuelle Bibliothekskonto nicht verfügbar."
ils_connection_failed = "Unser Bibliotheksverwaltungssystem ist momentan wegen Wartungsarbeiten nicht verfügbar."
ils_offline_holdings_message = "Bestandes- und Verfügbarkeitsinformationen können momentan leider nicht angezeigt werden. Wir entschuldigen uns für die Umstände und stehen für weitere Fragen gerne zur Verfügung:"
......
......@@ -510,6 +510,7 @@ ill_request_processed = "Processed"
ill_request_profile_html = "For interlibrary loan request information, please establish your <a href="%%url%%">Library Catalog Profile</a>."
ill_request_submit_text = "Place Request"
Illustrated = "Illustrated"
ils_account_create_error = "Your account could not be created in our library management system. If the problem persists, please contact your library."
ils_action_unavailable = "The requested function is not available with the active library card."
ils_connection_failed = "Connection to the library management system failed. Information related to your library account cannot be displayed. If the problem persists, please contact your library."
ils_offline_holdings_message = "Holdings and item availability information is currently unavailable. Please accept our apologies for any inconvenience this may cause and contact us for further assistance:"
......
......@@ -18,6 +18,20 @@ $config = [
],
],
],
'alma-webhook' => [
'type' => 'Zend\Router\Http\Segment',
'options' => [
'route' => '/Alma/Webhook/[:almaWebhookAction]',
'constraints' => [
'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
],
'defaults' => [
'controller' => 'Alma',
'action' => 'Webhook',
],
],
],
'content-page' => [
'type' => 'Zend\Router\Http\Segment',
'options' => [
......@@ -106,6 +120,7 @@ $config = [
'controllers' => [
'factories' => [
'VuFind\Controller\AjaxController' => 'VuFind\Controller\AjaxControllerFactory',
'VuFind\Controller\AlmaController' => 'VuFind\Controller\AbstractBaseFactory',
'VuFind\Controller\AlphabrowseController' => 'VuFind\Controller\AbstractBaseFactory',
'VuFind\Controller\AuthorController' => 'VuFind\Controller\AbstractBaseFactory',
'VuFind\Controller\AuthorityController' => 'VuFind\Controller\AbstractBaseFactory',
......@@ -157,6 +172,8 @@ $config = [
'aliases' => [
'AJAX' => 'VuFind\Controller\AjaxController',
'ajax' => 'VuFind\Controller\AjaxController',
'Alma' => 'VuFind\Controller\AlmaController',
'alma' => 'VuFind\Controller\AlmaController',
'Alphabrowse' => 'VuFind\Controller\AlphabrowseController',
'alphabrowse' => 'VuFind\Controller\AlphabrowseController',
'Author' => 'VuFind\Controller\AuthorController',
......
<?php
/**
* Alma Database authentication class
*
* PHP version 5
*
* Copyright (C) AK Bibliothek Wien für Sozialwissenschaften 2018.
*
* 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 Authentication
* @author Michael Birkner <michael.birkner@akwien.at>
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link https://vufind.org/wiki/development:plugins:authentication_handlers Wiki
*/
namespace VuFind\Auth;
use VuFind\Exception\Auth as AuthException;
/**
* Authentication class for Alma. The VuFind database and the Alma API are
* combined for authentication by this classe.
*
* @category VuFind
* @package Authentication
* @author Michael Birkner <michael.birkner@akwien.at>
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link https://vufind.org/wiki/development:plugins:authentication_handlers Wiki
*/
class AlmaDatabase extends Database
{
/**
* ILS Authenticator
*
* @var \VuFind\Auth\ILSAuthenticator
*/
protected $authenticator;
/**
* Catalog connection
*
* @var \VuFind\ILS\Connection
*/
protected $catalog = null;
/**
* Alma driver
*
* @var \VuFind\ILS\Driver\Alma
*/
protected $almaDriver = null;
/**
* Alma config
*
* @var array
*/
protected $almaConfig = null;
/**
* Constructor
*
* @param \VuFind\ILS\Connection $connection The ILS connection
* @param \VuFind\Auth\ILSAuthenticator $authenticator The ILS authenticator
*/
public function __construct(
\VuFind\ILS\Connection $connection,
\VuFind\Auth\ILSAuthenticator $authenticator
) {
$this->catalog = $connection;
$this->authenticator = $authenticator;
$this->almaDriver = $connection->getDriver();
$this->almaConfig = $connection->getDriverConfig();
}
/**
* Create a new user account in Alma AND in the VuFind Database.
*
* @param \Zend\Http\PhpEnvironment\Request $request Request object containing
* new account details.
*
* @return NULL|\VuFind\Db\Row\User New user row.
*/
public function create($request)
{
// When in privacy mode, don't create an Alma account and delegate
// further code execution to the parent.
if ($this->getConfig()->Authentication->privacy) {
return parent::create($request);
}
// User variable
$user = null;
// Collect POST parameters from request
$params = $this->collectParamsFromRequest($request);
// Validate username and password
$this->validateUsernameAndPassword($params);
// Get the user table
$userTable = $this->getUserTable();
// Make sure parameters are correct
$this->validateParams($params, $userTable);
// Create user account in Alma
$almaAnswer = $this->almaDriver->createAlmaUser($params);
// Create user account in VuFind user table if Alma gave us an answer
if ($almaAnswer !== null) {
// If we got this far, we're ready to create the account:
$user = $this->createUserFromParams($params, $userTable);
// Add the Alma primary ID as cat_id to the VuFind user table
$user->cat_id = $almaAnswer->primary_id ?? null;
// Save the new user to the user table
$user->save();
// Save the credentials to cat_username and cat_password to bypass
// the ILS login screen from VuFind
$user->saveCredentials($params['username'], $params['password']);
} else {
throw new AuthException($this->translate('ils_account_create_error'));
}
return $user;
}
}
......@@ -131,4 +131,19 @@ class Factory
$sm->get('Zend\Session\SessionManager')
);
}
/**
* Construct the AlmaDatabase plugin.
*
* @param ServiceManager $sm Service manager.
*
* @return AlmaDatabase
*/
public static function getAlmaDatabase(ServiceManager $sm)
{
return new AlmaDatabase(
$sm->get('VuFind\ILS\Connection'),
$sm->get('VuFind\Auth\ILSAuthenticator')
);
}
}
......@@ -44,6 +44,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager
* @var array
*/
protected $aliases = [
'almadatabase' => 'VuFind\Auth\AlmaDatabase',
'cas' => 'VuFind\Auth\CAS',
'choiceauth' => 'VuFind\Auth\ChoiceAuth',
'database' => 'VuFind\Auth\Database',
......@@ -65,6 +66,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager
* @var array
*/
protected $factories = [
'VuFind\Auth\AlmaDatabase' => 'VuFind\Auth\Factory::getAlmaDatabase',
'VuFind\Auth\CAS' => 'Zend\ServiceManager\Factory\InvokableFactory',
'VuFind\Auth\ChoiceAuth' => 'VuFind\Auth\Factory::getChoiceAuth',
'VuFind\Auth\Database' => 'Zend\ServiceManager\Factory\InvokableFactory',
......
<?php
/**
* Alma controller
*
* PHP version 5
*
* Copyright (C) AK Bibliothek Wien für Sozialwissenschaften 2018.
*
* 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
* @author Michael Birkner <michael.birkner@akwien.at>
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link https://vufind.org/wiki/development:plugins:controllers Wiki
*/
namespace VuFind\Controller;
use Zend\ServiceManager\ServiceLocatorInterface;
/**
* Alma controller, mainly for webhooks.
*
* @category VuFind
* @package Controller
* @author Michael Birkner <michael.birkner@akwien.at>
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link https://vufind.org/wiki/development:plugins:controllers Wiki
*/
class AlmaController extends AbstractBase
{
/**
* Http service
*
* @var \VuFindHttp\HttpService
*/
protected $httpService;
/**
* Http response
*
* @var \Zend\Http\PhpEnvironment\Response
*/
protected $httpResponse;
/**
* Http headers
*
* @var \Zend\Http\Headers
*/
protected $httpHeaders;
/**
* Configuration from config.ini
*
* @var \Zend\Config\Config
*/
protected $config;
/**
* Alma.ini config
*
* @var \Zend\Config\Config
*/
protected $configAlma;
/**
* User table
*
* @var \VuFind\Db\Table\User
*/
protected $userTable;
/**
* Alma Controler constructor.
*
* @param ServiceLocatorInterface $sm The ServiceLocatorInterface
*/
public function __construct(ServiceLocatorInterface $sm)
{
parent::__construct($sm);
$this->httpResponse = $this->getResponse();
$this->httpHeaders = $this->httpResponse->getHeaders();
$this->config = $this->getConfig('config');
$this->configAlma = $this->getConfig('Alma');
$this->userTable = $this->getTable('user');
}
/**
* Action that is executed when the webhook page is called.
*
* @return \Zend\Http\Response|NULL
*/
public function webhookAction()
{
// Request from external
$request = $this->getRequest();
// Get request method (GET, POST, ...)
$requestMethod = $request->getMethod();
// Get request body if method is POST and is not empty
$requestBodyJson = null;
if ($request->getContent() != null
&& !empty($request->getContent())
&& $requestMethod == 'POST'
) {
try {
$this->checkMessageSignature($request);
} catch (\VuFind\Exception\Forbidden $ex) {
return $this->createJsonResponse(
'Access to Alma Webhook is forbidden. ' .
'The message signature is not correct.', 403
);
}
$requestBodyJson = json_decode($request->getContent());
}
// Get webhook action
$webhookAction = $requestBodyJson->action ?? null;
// Perform webhook action
switch ($webhookAction) {
case 'USER':
$accessPermission = 'access.alma.webhook.user';
try {
$this->checkPermission($accessPermission);
} catch (\VuFind\Exception\Forbidden $ex) {
return $this->createJsonResponse(
'Access to Alma Webhook \'' . $webhookAction . '\' forbidden. ' .
'Set permission \'' . $accessPermission .
'\' in \'permissions.ini\'.', 403
);
}
return $this->webhookUser($requestBodyJson);
break;
case 'JOB_END':
case 'NOTIFICATION':
case 'LOAN':
case 'REQUEST':
case 'BIB':
case 'ITEM':
return $this->webhookNotImplemented($webhookAction);
break;
default:
$accessPermission = 'access.alma.webhook.challenge';
try {
$this->checkPermission($accessPermission);
} catch (\VuFind\Exception\Forbidden $ex) {
return $this->createJsonResponse(
'Access to Alma Webhook challenge forbidden. Set permission \'' .
$accessPermission . '\' in \'permissions.ini\'.', 403
);
}
return $this->webhookChallenge();
break;
}
}
/**
* Webhook actions related to a newly created, updated or deleted user in Alma.
*
* @param mixed $requestBodyJson A JSON string decode with json_decode()
*
* @return NULL|\Zend\Http\Response
*/
protected function webhookUser($requestBodyJson)
{
// Initialize user variable that should hold the user table row
$user = null;
// Initialize response variable
$jsonResponse = null;
// Get method from webhook (e. g. "create" for "new user")
$method = $requestBodyJson->webhook_user->method ?? null;
// Get primary ID
$primaryId = $requestBodyJson->webhook_user->user->primary_id ?? null;
if ($method == 'CREATE' || $method == 'UPDATE') {
// Get username (could e. g. be the barcode)
$username = null;
$userIdentifiers = $requestBodyJson->webhook_user->user->user_identifier
?? null;
$idTypeConfig = $this->configAlma->NewUser->idType ?? null;
foreach ($userIdentifiers as $userIdentifier) {
$idTypeHook = $userIdentifier->id_type->value ?? null;
if ($idTypeHook != null
&& $idTypeHook == $idTypeConfig
&& $username == null
) {
$username = $userIdentifier->value ?? null;
}
}
// Use primary ID as username as a fallback if no other
// username ID is available
$username = ($username == null) ? $primaryId : $username;
// Get user details from Alma Webhook message
$firstname = $requestBodyJson->webhook_user->user->first_name ?? null;
$lastname = $requestBodyJson->webhook_user->user->last_name ?? null;
$allEmails = $requestBodyJson->webhook_user->user->contact_info->email
?? null;
$email = null;
foreach ($allEmails as $currentEmail) {
$preferred = $currentEmail->preferred ?? false;
if ($preferred && $email == null) {
$email = $currentEmail->email_address ?? null;
}
}
if ($method == 'CREATE') {
$user = $this->userTable->getByUsername($username, true);
}
if ($method == 'UPDATE') {
$user = $this->userTable->getByCatalogId($primaryId);
}
if ($user) {
$user->username = $username;
$user->firstname = $firstname;
$user->lastname = $lastname;
$user->email = $email;
$user->cat_id = $primaryId;
$user->cat_username = $username;
try {
$user->save();
if ($method == 'CREATE') {
$this->sendSetPasswordEmail($user, $this->config);
}
$jsonResponse = $this->createJsonResponse(
'Successfully ' . strtolower($method) .
'd user with primary ID \'' . $primaryId .
'\' | username \'' . $username . '\'.', 200
);
} catch (\Exception $ex) {
$jsonResponse = $this->createJsonResponse(
'Error when saving new user with primary ID \'' .
$primaryId . '\' | username \'' . $username .
'\' to VuFind database and sending the welcome email: ' .
$ex->getMessage() . '. ',
400
);
}
} else {
$jsonResponse = $this->createJsonResponse(
'User with primary ID \'' . $primaryId . '\' | username \'' .
$username . '\' was not found in VuFind database and ' .
'therefore could not be ' . strtolower($method) . 'd.',
404
);
}
} elseif ($method == 'DELETE') {
$user = $this->userTable->getByCatalogId($primaryId);
if ($user) {
$rowsAffected = $user->delete();
if ($rowsAffected == 1) {
$jsonResponse = $this->createJsonResponse(
'Successfully deleted use with primary ID \'' . $primaryId .
'\' in VuFind.', 200
);
} else {
$jsonResponse = $this->createJsonResponse(
'Problem when deleting user with \'' . $primaryId .
'\' in VuFind. It is expected that only 1 row of the ' .
'VuFind user table is affected by the deletion. But ' .
$rowsAffected . ' were affected. Please check the status ' .
'of the user in the VuFind database.', 400
);
}
} else {
$jsonResponse = $this->createJsonResponse(
'User with primary ID \'' . $primaryId . '\' was not found in ' .
'VuFind database and therefore could not be deleted.', 404
);
}
}
return $jsonResponse;
}
/**
* The webhook challenge. This is used to activate the webhook in Alma. Without
* activating it, Alma will not send its webhook messages to VuFind.
*
* @return \Zend\Http\Response
*/
protected function webhookChallenge()
{
// Get challenge string from the get parameter that Alma sends us. We need to
// return this string in the return message.
$secret = $this->params()->fromQuery('challenge');
// Create the return array
$returnArray = [];
if (isset($secret) && !empty(trim($secret))) {
$returnArray['challenge'] = $secret;
$this->httpResponse->setStatusCode(200);
} else {
$returnArray['error'] = 'GET parameter \'challenge\' is empty, not ' .
'set or not available when receiving webhook challenge from Alma.';
$this->httpResponse->setStatusCode(500);
}
// Remove null from array
$returnArray = array_filter($returnArray);
// Create return JSON value and set it to the response
$returnJson = json_encode($returnArray, JSON_PRETTY_PRINT);
$this->httpHeaders->addHeaderLine('Content-type', 'application/json');
$this->httpResponse->setContent($returnJson);
return $this->httpResponse;
}
/**
* Send the "set password email" to a new user that was created in Alma and sent
* to VuFind via webhook.
*
* @param \VuFind\Db\Row\User $user A user row object from the VuFind
* user table.
* @param \Zend\Config\Config $config A config object of config.ini
*
* @return void
*/
protected function sendSetPasswordEmail($user, $config)
{
// If we can't find a user
if (null == $user) {
error_log(
'Could not send the email to new user for setting the ' .
'password because the user object was not found.'
);
} else {
// Attempt to send the email
try {
// Create a fresh hash
$user->updateHash();
$config = $this->getConfig();
$renderer = $this->getViewRenderer();
$method = $this->getAuthManager()->getAuthMethod();
// Custom template for emails (text-only)
$message = $renderer->render(
'Email/new-user-welcome.phtml', [
'library' => $config->Site->title,
'firstname' => $user->firstname,
'lastname' => $user->lastname,
'username' => $user->username,
'url' => $this->getServerUrl('myresearch-verify') . '?hash=' .
$user->verify_hash . '&auth_method=' . $method
]
);
// Send the email
$this->serviceLocator->get('VuFind\Mailer\Mailer')->send(
$user->email, $config->Site->email,
$this->translate(
'new_user_welcome_subject',
['%%library%%' => $config->Site->title]
),
$message
);
} catch (\VuFind\Exception\Mail $e) {
error_log(
'Could not send the \'set-password-email\' to user with ' .
'primary ID \'' . $user->cat_id . '\' | username \'' .
$user->username . '\': ' . $e->getMessage()
);
}
}
}
/**
* Create a HTTP response with JSON content and HTTP status codes that Alma takes
* as "answer" to its webhook calls.
*
* @param string $text The text that should be sent back to Alma
* @param int $httpStatusCode The HTTP status code that should be sent back
* to Alma
*
* @return \Zend\Http\Response
*/
protected function createJsonResponse($text, $httpStatusCode)
{
$returnArray = [];
$returnArray[] = $text;
$returnJson = json_encode($returnArray, JSON_PRETTY_PRINT);
$this->httpHeaders->addHeaderLine('Content-type', 'application/json');
$this->httpResponse->setStatusCode($httpStatusCode);
$this->httpResponse->setContent($returnJson);
return $this->httpResponse;
}
/**
* A default message to be sent back to Alma if an action for a certain webhook
* type is not implemented (yet).
*
* @param string $webhookType The type of the webhook
*
* @return \Zend\Http\Response
*/
protected function webhookNotImplemented($webhookType)
{
return $this->createJsonResponse(
$webhookType . ' Alma Webhook is not (yet) implemented in VuFind.', 400
);
}
/**
* Helper function to check access permissions defined in permissions.ini.
* The function validateAccessPermission() will throw an exception that can be
* catched when the permission is denied.
*
* @param string $accessPermission The permission name from permissions.ini that
* should be checked.
*
* @return void
*/
protected function checkPermission($accessPermission)
{
$this->accessPermission = $accessPermission;
$this->accessDeniedBehavior = 'exception';
$this->validateAccessPermission($this->getEvent());
}
/**
* Signing and hashing the body content of the Alma POST request with the
* webhook secret in Alma.ini. The calculated hash value must be the same as
* the 'X-Exl-Signature' in the request header. This is a security measure to
* be sure that the request comes from Alma.
*
* @param \Zend\Stdlib\RequestInterface $request The request from Alma.
*
* @throws \VuFind\Exception\Forbidden Throws forbidden exception
* if hash values are not the
* same.
*
* @return void
*/
protected function checkMessageSignature(\Zend\Stdlib\RequestInterface $request)
{
// Get request content
$requestBodyString = $request->getContent();
// Get hashed message signature from request header of Alma webhook request
$almaSignature = ($request->getHeaders()->get('X-Exl-Signature'))
? $request->getHeaders()->get('X-Exl-Signature')->getFieldValue()
: null;
// Get the webhook secret defined in Alma.ini
$secretConfig = $this->configAlma->Webhook->secret ?? null;
// Calculate hmac-sha256 hash from request body we get from Alma webhook and
// sign it with the Alma webhook secret from Alma.ini
$calculatedHash = base64_encode(
hash_hmac(
'sha256',
$requestBodyString,
$secretConfig,
true
)
);
// Check for correct signature
if ($almaSignature != $calculatedHash) {
error_log(
'[Alma] Unauthorized: Signature value not correct! ' .
'Hash from Alma: "' . $almaSignature . '". ' .
'Calculated hash: "' . $calculatedHash . '". ' .
'Body content for calculating the hash was: ' .
'"' . json_encode(
json_decode($requestBodyString),
JSON_UNESCAPED_UNICODE |
JSON_UNESCAPED_SLASHES
) . '"'
);
throw new \VuFind\Exception\Forbidden;
}
}
}
This diff is collapsed.
<?php
/**
* Factory for Alma ILS driver.
*
* PHP version 5
*
* Copyright (C) AK Bibliothek Wien für Sozialwissenschaften 2018.
*
* 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 ILS_Drivers
* @author Michael Birkner <michael.birkner@akwien.at>
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link https://vufind.org/wiki/development Wiki
*/
namespace VuFind\ILS\Driver;
use Interop\Container\ContainerInterface;
use Interop\Container\Exception\ContainerException;
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
use Zend\ServiceManager\Exception\ServiceNotFoundException;
use Zend\ServiceManager\Factory\FactoryInterface;
/**
* Alma ILS driver factory.
*
* @category VuFind
* @package ILS_Drivers
* @author Michael Birkner <michael.birkner@akwien.at>
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link https://vufind.org/wiki/development Wiki
*/
class AlmaFactory implements FactoryInterface
{
/**
* Create an object
*
* @param ContainerInterface $container Container interface
* @param string $requestedName Driver name
* @param null|array $options Options
*
* @return object Driver 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
) {
// Set up the driver with the date converter (and any extra parameters
// passed in as options):
$driver = new $requestedName(
$container->get('VuFind\Date\Converter'),
$container->get('VuFind\Config\PluginManager'),
...($options ?: [])
);
// Populate cache storage if a setCacheStorage method is present:
if (method_exists($driver, 'setCacheStorage')) {
$driver->setCacheStorage(
$container->get('VuFind\Cache\Manager')->getCache('object')
);
}
return $driver;
}
}
......@@ -45,6 +45,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager
*/
protected $aliases = [
'aleph' => 'VuFind\ILS\Driver\Aleph',
'alma' => 'VuFind\ILS\Driver\Alma',
'amicus' => 'VuFind\ILS\Driver\Amicus',
'daia' => 'VuFind\ILS\Driver\DAIA',
'demo' => 'VuFind\ILS\Driver\Demo',
......@@ -78,6 +79,7 @@ class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager
*/
protected $factories = [
'VuFind\ILS\Driver\Aleph' => 'VuFind\ILS\Driver\AlephFactory',
'VuFind\ILS\Driver\Alma' => 'VuFind\ILS\Driver\AlmaFactory',
'VuFind\ILS\Driver\Amicus' => 'Zend\ServiceManager\Factory\InvokableFactory',
'VuFind\ILS\Driver\DAIA' =>
'VuFind\ILS\Driver\DriverWithDateConverterFactory',
......
<?=$this->translate(
'new_user_welcome_text',
[
'%%library%%' => $this->library,
'%%firstname%%' => $this->firstname,
'%%lastname%%' => $this->lastname,
'%%username%%' => $this->username,
'%%url%%' => $this->url
]);
?>
\ No newline at end of file
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment