Skip to content

Commit

Permalink
Add check for FOLIO token expiration and add to search page load to s…
Browse files Browse the repository at this point in the history
…ave to session
  • Loading branch information
meganschanz committed Jan 13, 2025
1 parent 3d937b1 commit d76aa8c
Show file tree
Hide file tree
Showing 37 changed files with 119 additions and 111 deletions.
8 changes: 3 additions & 5 deletions module/VuFind/src/VuFind/ILS/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -968,11 +968,9 @@ public function getHoldsMode()
*/
public function getOfflineMode($healthCheck = false)
{
// If we have NoILS failover configured, force driver initialization so
// we can know we are checking the offline mode against the correct driver.
if ($this->hasNoILSFailover()) {
$this->getDriver();
}
// Always initialize the driver so that authentication tokens, if any,
// can be saved in the session cache
$this->getDriver();

// If we need to perform a health check, try to do a random item lookup
// before proceeding.
Expand Down
4 changes: 4 additions & 0 deletions module/VuFind/src/VuFind/ILS/Driver/AbstractAPI.php
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,16 @@ public function makeRequest(
$client->setParameterPost($params);
}
}
$startTime = microtime(true);
try {
$response = $client->send();
} catch (\Exception $e) {
$this->logError('Unexpected ' . $e::class . ': ' . (string)$e);
throw new ILSException('Error during send operation.');
}
$endTime = microtime(true);
$responseTime = $endTime - $startTime;
$this->debug('Request Response Time --- ' . $responseTime . ' seconds. ' . $path);
$code = $response->getStatusCode();
if (
!$response->isSuccess()
Expand Down
88 changes: 85 additions & 3 deletions module/VuFind/src/VuFind/ILS/Driver/Folio.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ class Folio extends AbstractAPI implements
*/
protected $token = null;

/**
* Authentication token expiration time
*
* @var string
*/
protected $tokenExpiration = null;

/**
* Factory function for constructing the SessionContainer.
*
Expand Down Expand Up @@ -282,15 +289,31 @@ public function preRequest(\Laminas\Http\Headers $headers, $params)
*/
protected function renewTenantToken()
{
// If not using legacy authentication, see if the token has expired before trying to renew it
if (! $this->useLegacyAuthentication() && ! $this->checkTenantTokenExpired()) {
$currentTime = gmdate('D, d-M-Y H:i:s T', strtotime('now'));
$this->debug(
'No need to renew token; not yet expired. ' . $currentTime . ' < ' . $this->tokenExpiration .
'Username: ' . $this->config['API']['username'] . ' Token: ' . substr($this->token, 0, 30) . '...'
);
return;
}
$startTime = microtime(true);
$this->token = null;
$response = $this->performOkapiUsernamePasswordAuthentication(
$this->config['API']['username'],
$this->getSecretFromConfig($this->config['API'], 'password')
);
$this->token = $this->extractTokenFromResponse($response);
$this->tokenExpiration =
$this->useLegacyAuthentication() ?
null : $this->extractTokenExpirationFromResponse($response);
$this->sessionCache->folio_token = $this->token;
$this->sessionCache->folio_token_expiration = $this->tokenExpiration;
$endTime = microtime(true);
$responseTime = $endTime - $startTime;
$this->debug(
'Token renewed. Username: ' . $this->config['API']['username'] .
'Token renewed in ' . $responseTime . ' seconds. Username: ' . $this->config['API']['username'] .
' Token: ' . substr($this->token, 0, 30) . '...'
);
}
Expand All @@ -304,15 +327,38 @@ protected function renewTenantToken()
*/
protected function checkTenantToken()
{
$response = $this->makeRequest('GET', '/users', [], [], [401, 403]);
if ($response->getStatusCode() >= 400) {
if ($this->useLegacyAuthentication()) {
$response = $this->makeRequest('GET', '/users', [], [], [401, 403]);
if ($response->getStatusCode() >= 400) {
$this->token = null;
$this->tokenExpiration = null;
$this->renewTenantToken();
return false;
}
return true;
}
if ($this->checkTenantTokenExpired()) {
$this->token = null;
$this->tokenExpiration = null;
$this->renewTenantToken();
return false;
}
return true;
}

/**
* Check if our token has expired. Return true if it has expired, false if it has not.
*
* @return bool
*/
protected function checkTenantTokenExpired()
{
return
$this->token == null ||
$this->tokenExpiration == null ||
strtotime('now') >= strtotime($this->tokenExpiration);
}

/**
* Initialize the driver.
*
Expand All @@ -326,6 +372,7 @@ public function init()
$this->sessionCache = $factory($this->tenant);
if ($this->sessionCache->folio_token ?? false) {
$this->token = $this->sessionCache->folio_token;
$this->tokenExpiration = $this->sessionCache->folio_token_expiration ?? null;
$this->debug(
'Token taken from cache: ' . substr($this->token, 0, 30) . '...'
);
Expand Down Expand Up @@ -1113,6 +1160,35 @@ protected function extractTokenFromResponse(Response $response): string
throw new \Exception('Could not find token in response');
}

/**
* Given a response from performOkapiUsernamePasswordAuthentication(),
* extract the token expiration time.
*
* @param Response $response Response from performOkapiUsernamePasswordAuthentication().
*
* @return string
*/
protected function extractTokenExpirationFromResponse(Response $response): string
{
$currentTime = gmdate('D, d-M-Y H:i:s T', strtotime('now'));

// If using legacy authentication, there is no option to renew tokens,
// so assume the token is expired as of now
if ($this->useLegacyAuthentication()) {
return $currentTime;
}
$folioUrl = $this->config['API']['base_url'];
$cookies = new \Laminas\Http\Cookies();
$cookies->addCookiesFromResponse($response, $folioUrl);
$results = $cookies->getAllCookies();
foreach ($results as $cookie) {
if ($cookie->getName() == 'folioAccessToken') {
return $cookie->getExpires();
}
}
throw new \Exception('Could not find token expiration in response');
}

/**
* Support method for patronLogin(): authenticate the patron with an Okapi
* login attempt. Returns a CQL query for retrieving more information about
Expand Down Expand Up @@ -1246,6 +1322,12 @@ protected function getPagedResults($responseKey, $interface, $query = [], $limit
{
$offset = 0;

// If we're using the new authentication method and our token has expired
// Renew it now before we make the call
if (! $this->useLegacyAuthentication() && $this->checkTenantTokenExpired()) {
$this->renewTenantToken();
}

do {
$json = $this->getResultPage($interface, $query, $offset, $limit);
$totalEstimate = $json->totalRecords ?? 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
[
{
"comment": "Initial token check"
},
{
"expectedPath": "/circulation/requests/request1",
"body": "{ \"requesterId\": \"foo\" }"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
[
{
"comment": "Initial token check"
},
{
"expectedPath": "/circulation/requests/request1",
"body": "{ \"requesterId\": \"foo\", \"itemId\": \"item1\" }"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
[
{
"comment": "simulate bad token",
"expectedPath": "/users",
"status": 400
},
{
"comment": "send new token",
"expectedMethod": "POST",
"expectedPath": "/authn/login-with-expiry",
"expectedParams": "{\"tenant\":\"config_tenant\",\"username\":\"config_username\",\"password\":\"config_password\"}",
"headers": { "Set-Cookie": "folioAccessToken=x-okapi-token-after-invalid; Max-Age=600; Expires=Fri, 22 Sep 2023 14:30:10 GMT; Path=/; Secure; HTTPOnly; SameSite=None" }
"headers": { "Set-Cookie": "folioAccessToken=x-okapi-token-after-invalid; Max-Age=600; Expires=Fri, 22 Sep 2090 14:30:10 GMT; Path=/; Secure; HTTPOnly; SameSite=None" }
},
{
"comment": "confirm that new token is used",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
[
{
"comment": "Initial token check",
"expectedPath": "/users",
"status": 200
},
{
"comment": "We're checking that the token was correctly cached from get-tokens.json.",
"expectedPath": "/circulation/loans",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
[
{
"comment": "Initial request for token"
},
{
"expectedPath": "/inventory/items/bc3fd525-4254-4075-845b-1428986d811b",
"expectedParams": {},
"comment": "Item with six boundWithTitles",
"status": 200,
"body": "{\"id\" : \"bc3fd525-4254-4075-845b-1428986d811b\", \"_version\" : \"2\", \"status\" : { \"name\" : \"Available\", \"date\" : \"2020-08-15T16:15:40.000+00:00\" }, \"administrativeNotes\" : [ ], \"title\" : \"Ueber sclaverei, sclaven-emancipation und die einwanderung \\\"freier neger\\\" nach den colonieen; aufzeichnungen eines weitgereisten.\", \"callNumber\" : \"326.08 S632 \", \"hrid\" : \"1015758\", \"contributorNames\" : [ ], \"formerIds\" : [ \"OLE-id-1015758\" ], \"discoverySuppress\" : false, \"holdingsRecordId\" : \"7c12cb78-1249-4ec1-a221-9a426822d866\", \"barcode\" : \"39151008725588\", \"itemLevelCallNumber\" : \"326.08 S632 v.10\", \"itemLevelCallNumberPrefix\" : \"\", \"itemLevelCallNumberTypeId\" : \"03dd64d0-5626-4ecd-8ece-4531e0069f35\", \"notes\" : [ { \"itemNoteTypeId\" : \"8f26b475-d7e3-4577-8bd0-c3d3bf44f73b\", \"note\" : \"1\", \"staffOnly\" : true } ], \"circulationNotes\" : [ ], \"tags\" : { \"tagList\" : [ ] }, \"yearCaption\" : [ ], \"electronicAccess\" : [ ], \"statisticalCodeIds\" : [ \"ba16cd17-fb83-4a14-ab40-23c7ffa5ccb5\" ], \"purchaseOrderLineIdentifier\" : null, \"materialType\" : { \"id\" : \"1a54b431-2e4f-452d-9cae-9cee66c9a892\", \"name\" : \"book\" }, \"permanentLoanType\" : { \"id\" : \"2b94c631-fca9-4892-a730-03ee529ffe27\", \"name\" : \"Can circulate\" }, \"metadata\" : { \"createdDate\" : \"2020-08-15T16:15:40.000+00:00\", \"createdByUserId\" : \"82dd3b08-c440-4aa1-b924-839c3ec2627c\", \"updatedDate\" : \"2021-06-09T14:52:07.240+00:00\", \"updatedByUserId\" : \"163580ba-a91d-4974-ad65-e68becf69904\" }, \"effectiveCallNumberComponents\" : { \"callNumber\" : \"326.08 S632 v.10\", \"prefix\" : null, \"suffix\" : null, \"typeId\" : \"03dd64d0-5626-4ecd-8ece-4531e0069f35\" }, \"effectiveShelvingOrder\" : \"3326.08 S632 V 210\", \"isBoundWith\" : true, \"boundWithTitles\" : [ { \"briefHoldingsRecord\" : { \"id\" : \"76b8c622-361b-4f2e-802c-1df39d0f04e3\", \"hrid\" : \"ho00003535456\" }, \"briefInstance\" : { \"id\" : \"12cb5553-c1bb-48c8-b439-aebc5202970f\", \"title\" : \"Slavery as it once prevailed in Massachusetts : A lecture for the Massachusetts Historical Society ...\", \"hrid\" : \"1093117\" } }, { \"briefHoldingsRecord\" : { \"id\" : \"7c12cb78-1249-4ec1-a221-9a426822d866\", \"hrid\" : \"1003416\" }, \"briefInstance\" : { \"id\" : \"f56d3ce3-b31f-4320-8e08-dfc2f9a96c4a\", \"title\" : \"Ueber sclaverei, sclaven-emancipation und die einwanderung \\\"freier neger\\\" nach den colonieen; aufzeichnungen eines weitgereisten.\", \"hrid\" : \"1093115\" } }, { \"briefHoldingsRecord\" : { \"id\" : \"8d0b4586-848f-4235-8a97-1ab0e28cbff2\", \"hrid\" : \"ho00003535455\" }, \"briefInstance\" : { \"id\" : \"6abe72a5-f518-408a-8a67-fe1ec15627b8\", \"title\" : \"Concerning a full understanding of the southern attitude toward slavery, by John Douglass Van Horne.\", \"hrid\" : \"1093116\" } }, { \"briefHoldingsRecord\" : { \"id\" : \"386ec94b-9387-4c38-b5b0-6bbebf75eeb3\", \"hrid\" : \"ho00003535457\" }, \"briefInstance\" : { \"id\" : \"1c4cda9b-3506-45e7-b444-0e901cc661e3\", \"title\" : \"American slavery : echoes and glimpses of prophecy / by Daniel S. Whitney.\", \"hrid\" : \"1093118\" } }, { \"briefHoldingsRecord\" : { \"id\" : \"fdbcf4ae-2435-437f-8d69-a013ef9e7b89\", \"hrid\" : \"ho00003535458\" }, \"briefInstance\" : { \"id\" : \"03684060-3bc0-4a66-874c-854e50ed84fe\", \"title\" : \"The Tract society and slavery. Speeches of Chief Justice Williams, Judge Parsons, and ex-Governor Ellsworth: delivered in the Center Church, Hartford, Conn., at the anniversary of the Hartford branch of the American Tract Society, January 9th, 1859.\", \"hrid\" : \"1093119\" } }, { \"briefHoldingsRecord\" : { \"id\" : \"4265ebf4-7fae-486f-9b7b-7a43ce9284ba\", \"hrid\" : \"ho00003535459\" }, \"briefInstance\" : { \"id\" : \"080e5167-7a50-4513-b0f3-0f5bf835df7b\", \"title\" : \"Case of Passmore Williamson : report of the proceedings on the writ of habeas corpus, issued by the Hon. John K. Kane, judge of the District Court of the United States for the Eastern District of Pennsylvania, in the case of the United States of America ex rel. John H. Wheeler vs. Passmore Williamson, including the several opinions delivered, and the arguments of counsel / reported by Arthur Cannon.\", \"hrid\" : \"1093120\" } } ], \"effectiveLocation\" : { \"id\" : \"2337edc1-d611-4024-be3c-0fe78e5d03ca\", \"name\" : \"LMC-B\" }}"
}
]
]
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
[
{
"comment": "Initial token check"
},
{
"expectedMethod": "GET",
"expectedPath": "\/instance-storage\/instances",
Expand Down Expand Up @@ -252,4 +249,4 @@
"bodyType": "json",
"status": 200
}
]
]
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
[
{
"comment": "Initial token check"
},
{
"expectedMethod": "GET",
"expectedPath": "\/instance-storage\/instances",
Expand Down Expand Up @@ -224,4 +221,4 @@
"bodyType": "json",
"status": 200
}
]
]
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
[
{
"comment": "Initial token check"
},
{
"expectedMethod": "GET",
"expectedPath": "\/instance-storage\/instances",
Expand Down Expand Up @@ -243,4 +240,4 @@
"bodyType": "json",
"status": 200
}
]
]
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
[
{
"comment": "Initial token check"
},
{
"expectedMethod": "GET",
"expectedPath": "\/instance-storage\/instances",
Expand Down Expand Up @@ -215,4 +212,4 @@
"bodyType": "json",
"status": 200
}
]
]
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
[
{
"comment": "Initial token check"
},
{
"expectedMethod": "GET",
"expectedPath": "\/instance-storage\/instances",
Expand Down Expand Up @@ -209,4 +206,4 @@
"bodyType": "json",
"status": 200
}
]
]
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
[
{
"comment": "Initial token check"
},
{
"comment": "User with 1 hold, hold is available for pickup until 2022-12-29T05:59:59.000+00:00",
"expectedPath": "/request-storage/requests",
Expand All @@ -13,4 +10,4 @@
"status": 200,
"body": "{\"requests\":[{\"id\":\"fake-request-num\",\"requestLevel\":\"Item\",\"requestType\":\"Page\",\"requestDate\":\"2022-12-20T05:55:33.000+00:00\",\"requesterId\":\"foo\",\"instanceId\":\"fake-instance-id\",\"holdingsRecordId\":\"fake-holding-id\",\"itemId\":\"fake-item-id\",\"status\":\"Open - Awaiting pickup\",\"position\":1,\"instance\":{\"title\":\"Presentation secrets : do what you never thought possible with your presentations \",\"identifiers\":[{\"value\":\"9781118034965 (pbk : alk paper)\",\"identifierTypeId\":\"id-type-id\"}]},\"item\":{\"barcode\":\"431332678\"},\"requester\":{\"firstName\":\"John\",\"lastName\":\"TestuserJohn\",\"barcode\":\"995324292\"},\"fulfilmentPreference\":\"Hold Shelf\",\"holdShelfExpirationDate\":\"2022-12-29T05:59:59.000+00:00\",\"pickupServicePointId\":\"psp-id\",\"metadata\":{\"createdDate\":\"2022-12-20T05:55:34.325+00:00\",\"createdByUserId\":\"fake-user-2\",\"updatedDate\":\"2022-12-20T06:07:27.712+00:00\",\"updatedByUserId\":\"fake-user-2\"}}],\"totalRecords\":1}"
}
]
]
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
[
{
"comment": "Initial token check"
},
{
"comment": "User with 1 hold, hold is available for pickup until 2022-12-29T05:59:59.000+00:00",
"expectedPath": "/request-storage/requests",
Expand All @@ -13,4 +10,4 @@
"status": 200,
"body": "{\"requests\":[{\"id\":\"fake-request-num\",\"requestLevel\":\"Item\",\"requestType\":\"Page\",\"requestDate\":\"2022-12-20T05:55:33.000+00:00\",\"requesterId\":\"foo\",\"instanceId\":\"fake-instance-id\",\"holdingsRecordId\":\"fake-holding-id\",\"itemId\":\"fake-item-id\",\"status\":\"Open - Awaiting pickup\",\"position\":1,\"instance\":{\"title\":\"Presentation secrets : do what you never thought possible with your presentations \",\"identifiers\":[{\"value\":\"9781118034965 (pbk : alk paper)\",\"identifierTypeId\":\"id-type-id\"}]},\"item\":{\"barcode\":\"431332678\"},\"requester\":{\"firstName\":\"John\",\"lastName\":\"TestuserJohn\",\"barcode\":\"995324292\"},\"fulfilmentPreference\":\"Hold Shelf\",\"holdShelfExpirationDate\":\"2022-12-29T05:59:59.000+00:00\",\"pickupServicePointId\":\"psp-id\",\"metadata\":{\"createdDate\":\"2022-12-20T05:55:34.325+00:00\",\"createdByUserId\":\"fake-user-2\",\"updatedDate\":\"2022-12-20T06:07:27.712+00:00\",\"updatedByUserId\":\"fake-user-2\"}}],\"totalRecords\":1}"
}
]
]
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
[
{
"comment": "Initial token check"
},
{
"comment": "User with 1 hold, item is currently in_transit",
"expectedPath": "/request-storage/requests",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
[
{
"comment": "Initial token check"
},
{
"comment": "User with 3 holds, items are currently in_transit",
"expectedPath": "/request-storage/requests",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
[
{
"comment": "Initial token check"
},
{
"comment": "Page 1 results",
"expectedPath": "/request-storage/requests",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
[
{
"comment": "Initial token check"
},
{
"comment": "User with 2 holds, items are currently in_transit",
"expectedPath": "/request-storage/requests",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
[
{
"comment": "Initial token check"
},
{
"comment": "User with 1 hold, item is currently in_transit",
"expectedPath": "/request-storage/requests",
Expand All @@ -13,4 +10,4 @@
"status": 200,
"body": "{\"requests\":[{\"id\":\"fake-request-num\",\"requestLevel\":\"Item\",\"requestType\":\"Page\",\"requestDate\":\"2022-11-07T09:23:46.508+00:00\",\"requesterId\":\"foo\",\"instanceId\":\"fake-instance-id\",\"holdingsRecordId\":\"fake-holdings-id\",\"itemId\":\"fake-item-id\",\"status\":\"Open - In transit\",\"position\":1,\"instance\":{\"title\":\"Basic economics : a common sense guide to the economy \",\"identifiers\":[{\"value\":\"0465060730 :\",\"identifierTypeId\":\"id-type-id\"}]},\"item\":{\"barcode\":\"8026657307\"},\"requester\":{\"firstName\":\"John\",\"lastName\":\"Testuser\",\"barcode\":\"995324292\"},\"fulfilmentPreference\":\"Hold Shelf\",\"pickupServicePointId\":\"b61315ba-a759-42de-9303-0d51cbd4edbb\",\"metadata\":{\"createdDate\":\"2022-11-07T09:23:48.197+00:00\",\"createdByUserId\":\"fake-user-1\",\"updatedDate\":\"2022-12-20T06:02:03.248+00:00\",\"updatedByUserId\":\"fake-user-2\"}}],\"totalRecords\":1}"
}
]
]
Loading

0 comments on commit d76aa8c

Please sign in to comment.