Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check expiration time in FOLIO token when using login-with-expiry #4186

Open
wants to merge 11 commits into
base: dev
Choose a base branch
from
Open
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()) {
meganschanz marked this conversation as resolved.
Show resolved Hide resolved
$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);
meganschanz marked this conversation as resolved.
Show resolved Hide resolved
$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;
meganschanz marked this conversation as resolved.
Show resolved Hide resolved
}
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);
meganschanz marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* 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();
}
}
demiankatz marked this conversation as resolved.
Show resolved Hide resolved
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();
demiankatz marked this conversation as resolved.
Show resolved Hide resolved
}

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
Loading