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 12 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
97 changes: 80 additions & 17 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,26 @@ 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->sessionCache->folio_token = $this->token;
$this->setTokenValuesFromResponse($response);
$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 +322,34 @@ protected function renewTenantToken()
*/
protected function checkTenantToken()
{
$response = $this->makeRequest('GET', '/users', [], [], [401, 403]);
if ($response->getStatusCode() >= 400) {
$this->token = null;
if ($this->useLegacyAuthentication()) {
$response = $this->makeRequest('GET', '/users', [], [], [401, 403]);
if ($response->getStatusCode() < 400) {
return true;
}
$this->token = $this->tokenExpiration = null;
meganschanz marked this conversation as resolved.
Show resolved Hide resolved
}
if ($this->checkTenantTokenExpired()) {
$this->token = $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 +363,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 @@ -1090,27 +1128,52 @@ protected function performOkapiUsernamePasswordAuthentication(string $username,

/**
* Given a response from performOkapiUsernamePasswordAuthentication(),
* extract the token value.
* extract the requested cookie.
*
* @param Response $response Response from performOkapiUsernamePasswordAuthentication().
* @param Response $response Response from performOkapiUsernamePasswordAuthentication().
* @param string $cookieName Name of the cookie to get from the response.
*
* @return string
* @return \Laminas\Http\Header\SetCookie
*/
protected function extractTokenFromResponse(Response $response): string
protected function getCookieByName(Response $response, string $cookieName): \Laminas\Http\Header\SetCookie
{
if ($this->useLegacyAuthentication()) {
return $response->getHeaders()->get('X-Okapi-Token')->getFieldValue();
}
$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->getValue();
if ($cookie->getName() == $cookieName) {
return $cookie;
}
}
throw new \Exception('Could not find token in response');
throw new \Exception('Could not find ' . $cookieName . ' in response');
meganschanz marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Given a response from performOkapiUsernamePasswordAuthentication(),
* extract and save authentication data we want to preserve.
*
* @param Response $response Response from performOkapiUsernamePasswordAuthentication().
*
* @return null
*/
protected function setTokenValuesFromResponse(Response $response)
{
$currentTime = gmdate('D, d-M-Y H:i:s T', strtotime('now'));

if ($this->useLegacyAuthentication()) {
$this->token = $response->getHeaders()->get('X-Okapi-Token')->getFieldValue();
$this->tokenExpiration = $currentTime;
meganschanz marked this conversation as resolved.
Show resolved Hide resolved
} elseif ($cookie = $this->getCookieByName($response, 'folioAccessToken')) {
$this->token = $cookie->getValue();
$this->tokenExpiration = $cookie->getExpires();
}
if ($this->token != null && $this->tokenExpiration != null) {
$this->sessionCache->folio_token = $this->token;
$this->sessionCache->folio_token_expiration = $this->tokenExpiration;
} else {
throw new \Exception('Could not find token data in response');
}
}

/**
Expand All @@ -1132,7 +1195,7 @@ protected function patronLoginWithOkapi($username, $password)
$query = 'username == ' . $username;
// Replace admin with user as tenant if configured to do so:
if ($this->config['User']['use_user_token'] ?? false) {
$this->token = $this->extractTokenFromResponse($response);
$this->setTokenValuesFromResponse($response);
$debugMsg .= ' Token: ' . substr($this->token, 0, 30) . '...';
}
$this->debug($debugMsg);
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,21 @@
[
{
"comment": "simulate bad token",
"expectedPath": "/users",
"status": 400
"comment": "make call with expired token",
"expectedPath": "/service-points",
"expectedHeaders": { "X-Okapi-Token": "x-okapi-token-config-tenant" },
"expectedParams": {
"query": "pickupLocation=true",
"offset": 0,
"limit": 1000
},
"body": "{ \"servicepoints\": [] }"
},
{
"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