-
Notifications
You must be signed in to change notification settings - Fork 386
API key implementation #4478
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
API key implementation #4478
Changes from 19 commits
feab404
fa5bb48
b235bf8
78d900b
65ea6b6
b8da4a8
4b759ec
8efab0a
fe4853c
f366036
f8c2f82
7e73ab5
3df7ca7
3f8d251
1517201
c39ac37
2ffc3cc
2b717d9
70476cf
fd583c3
94566d6
05789f4
c0495ef
b2220f4
8d2be49
4b0332a
10a0cad
50acf3e
c1aab9b
7601d58
0bf6250
4529be8
35aaa04
c5e1938
a039bda
80159bb
8d20379
860bb87
b96795e
81ab265
ce26530
e1f2b10
7e38cb7
8590e2e
5fb9001
d962aed
70507bc
653d92b
f671a2f
0422818
27413c2
923ead1
9c5777e
99b302c
348a6b7
73e3f64
171dc81
814f644
a9e0e8a
f0f06bc
9706d07
f571a03
189feda
e4886c0
e893d55
50d5341
a62dc62
4a67a17
f415ccd
c200917
0dfff64
b9a4dc0
da48185
ea80494
0723215
6a5b708
ebbee27
fad326d
dd94748
feb9525
310150e
6bdae30
3f5cbbe
6dff6ca
f3480ff
21a768f
04ff35f
df4f135
aee0d60
c85d988
dbc025d
17e8ab0
1230bfc
38c7da4
0a2e9ae
fe26818
a26fa33
d317d7c
b95ddee
5a17412
bfc3674
56e90e0
b2ec4cf
0ee30fe
02da0dd
422a226
f78f376
19fcba2
4c61c48
b326a62
00de67f
ddf8262
8f4fca5
f8c5810
06dbfe5
50b7c8e
89b29a4
7b42346
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2641,6 +2641,20 @@ description = "The REST API provides access to search functions and records cont | |
| ; URL pointing to a Terms of Service page (optional, default is none): | ||
| ;termsOfServiceUrl = "https://something" | ||
|
|
||
| ; These settings control usage of API keys | ||
| [API_Keys] | ||
| ; Mode for using API keys. | ||
| ; 'disabled' means API keys are not in use and cannot be created. Default. | ||
| ; 'enabled' allows users to create API keys and use them, but it is not mandatory. | ||
| ; 'enforced' forces users to create and provide an API key in X-API-KEY header field. | ||
LuomaJuha marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ;mode = 'enabled' | ||
| ; Token salt to add when generating a new API key for a user. | ||
EreMaijala marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
demiankatz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ;token_salt = | ||
| ; Which header field key should the API key be provided. Default is X-API-KEY. | ||
LuomaJuha marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ;header_field = 'X-API-KEY' | ||
| ; Uncomment to allow logging requests using API keys | ||
|
||
| ;log_requests = true | ||
|
|
||
| [Sorting] | ||
| ; By default, VuFind sorts text in a locale-agnostic way; if this setting is | ||
| ; turned on, the current user-selected locale will impact sort order. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "حذف" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "মুছুন" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Eliminar" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Odstranit" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Dileu" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Slet" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Löschen" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Διαγραφή" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| api_key = "API key" | ||
| api_key_delete = "Delete" | ||
demiankatz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| api_key_deletion_failed = "API key deletion failed" | ||
|
||
| api_key_deletion_success = "API key was deleted successfully" | ||
| api_key_generate = "Generate new" | ||
demiankatz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| api_key_generation_failed = "API key generation failed" | ||
|
||
| api_key_generation_success = "API key was generated successfully. Key will be displayed only once so save it: %%TOKEN%%" | ||
| api_key_locked = "API key is locked. Please contact support if you have questions" | ||
| settings = "Developer settings" | ||
| show_developer_settings = "Show developer settings" | ||
| verify_email_address = "Please verify your email-address to generate an API key" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Borrar" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Ezabatu" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| api_key = "API-avain" | ||
| api_key_delete = "Poista" | ||
| api_key_deletion_failed = "API-avaimen poistaminen epäonnistui" | ||
| api_key_deletion_success = "API-avaimen poistaminen onnistui" | ||
| api_key_generate = "Luo uusi" | ||
| api_key_generation_failed = "API-avaimen luonti epäonnistui" | ||
| api_key_generation_success = "API-avaimen luonti onnistui. Avain näytetään vain kerran, joten tallenna se muistiin: %%TOKEN%%" | ||
| api_key_locked = "API-avain on lukittu. Ota yhteyttä tukeen, jos sinulla on kysyttävää" | ||
| settings = "Kehittäjäasetukset" | ||
| show_developer_settings = "Näytä kehittäjäasetukset" | ||
| verify_email_address = "Vahvista sähköpostiosoitteesi luodaksesi API-avaimen" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Supprimer" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Scrios" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Borrar" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "מחק" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "हटाना" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Izbriši" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Ջնջել" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Cancella" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "削除" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Muku" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Устгах" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Verwijderen" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Usuń" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Apagar" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Apagar" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Удалить" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Sihko" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Zbriši" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Radera" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Sil" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Видалити" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "Xóa" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "删除" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| api_key_delete = "刪除" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,172 @@ | ||
| <?php | ||
|
|
||
| /** | ||
| * Service for managing API keys | ||
| * | ||
| * PHP version 8 | ||
| * | ||
| * Copyright (C) The National Library of Finland 2025. | ||
| * | ||
| * 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 Service | ||
| * @author Juha Luoma <[email protected]> | ||
| * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License | ||
| * @link https://vufind.org/wiki/development:plugins:database_gateways Wiki | ||
| */ | ||
|
|
||
| namespace VuFind\ApiKey; | ||
|
|
||
| use VuFind\Db\Entity\AccessTokenEntityInterface; | ||
| use VuFind\Db\Entity\UserEntityInterface; | ||
| use VuFind\Db\Service\AccessTokenService; | ||
| use VuFind\Db\Service\DbServiceAwareInterface; | ||
| use VuFind\Db\Service\DbServiceAwareTrait; | ||
|
|
||
| /** | ||
| * Service for managing API keys | ||
| * | ||
| * @category VuFind | ||
| * @package Service | ||
| * @author Juha Luoma <[email protected]> | ||
| * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License | ||
| * @link https://vufind.org/wiki/development:plugins:database_gateways Wiki | ||
| */ | ||
| class ApiKeyService implements DbServiceAwareInterface | ||
| { | ||
| use DbServiceAwareTrait; | ||
demiankatz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * Constructor. | ||
| * | ||
| * @param array $apiKeySettings Section API_Keys from main configuration. | ||
| */ | ||
| public function __construct( | ||
| protected array $apiKeySettings | ||
| ) { | ||
| } | ||
|
|
||
| /** | ||
| * Generate a new api key token | ||
| * | ||
| * @param UserEntityInterface $user User to create salt for | ||
| * | ||
| * @return string | ||
| */ | ||
| protected function createRandomToken(UserEntityInterface $user): string | ||
| { | ||
| $salt = $this->apiKeySettings['token_salt'] ?? null; | ||
| if (!$salt) { | ||
| throw new \Exception('APIKeyService: Salt missing'); | ||
| } | ||
| $valuesForToken = [ | ||
| $user->getEmailVerified()->format('Y-m-d'), | ||
| $user->getFirstname(), | ||
| $user->getLastname(), | ||
| (string)strtotime('now'), | ||
| $salt, | ||
| ]; | ||
| return hash('sha256', implode('|', $valuesForToken)); | ||
| } | ||
|
|
||
| /** | ||
| * Retrieve an API key for a user. Return associative array containing token, revoked or empty | ||
| * array if not found. | ||
| * | ||
| * @param UserEntityInterface $user User | ||
| * | ||
| * @return ?AccessTokenEntityInterface | ||
| */ | ||
| public function getApiKeyForUser(UserEntityInterface $user): ?AccessTokenEntityInterface | ||
| { | ||
| return $this->getDbService(AccessTokenService::class)->getByIdAndType( | ||
| (string)$user->getId(), | ||
| AccessTokenService::TYPE_API_KEY, | ||
| false | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Check if API key is valid to be used. | ||
| * | ||
| * @param string $token Token to search for. | ||
| * | ||
| * @return bool If exists and the access token has not been revoked. | ||
| */ | ||
| public function isTokenValid(string $token): bool | ||
| { | ||
| $token = $this->getDbService(AccessTokenService::class)->getByDataAndType( | ||
| $token, | ||
| AccessTokenService::TYPE_API_KEY | ||
| ); | ||
| return $token && !$token->isRevoked(); | ||
| } | ||
|
|
||
| /** | ||
| * Validate user can use API keys. It is expected that the user has a verified email address. | ||
| * | ||
| * @param UserEntityInterface $user User | ||
| * | ||
| * @return bool | ||
| */ | ||
| public function isUserValid(UserEntityInterface $user): bool | ||
| { | ||
| return $user->getEmailVerified() !== null; | ||
| } | ||
|
|
||
| /** | ||
| * Generate an API key for a user. | ||
| * | ||
| * @param UserEntityInterface $user User | ||
| * | ||
| * @return string|false API key token on success, false on failure | ||
| */ | ||
| public function generateApiKeyForUser(UserEntityInterface $user): string|false | ||
| { | ||
| // Check if the user has an existing token and the token has not been revoked. | ||
| $token = $this->getDbService(AccessTokenService::class)->getByIdAndType( | ||
| (string)$user->getId(), | ||
| AccessTokenService::TYPE_API_KEY | ||
| ); | ||
| if (!$token || $token->isRevoked()) { | ||
| return false; | ||
| } | ||
| $tokenHash = $this->createRandomToken($user); | ||
| $token->setData($tokenHash); | ||
| $token->setUser($user); | ||
| $this->getDbService(AccessTokenService::class)->persistEntity($token); | ||
| return $tokenHash; | ||
| } | ||
|
|
||
| /** | ||
| * Delete an API key for a user | ||
| * | ||
| * @param UserEntityInterface $user User | ||
| * | ||
| * @return bool | ||
| */ | ||
| public function deleteApiKeyForUser(UserEntityInterface $user): bool | ||
| { | ||
| $token = $this->getDbService(AccessTokenService::class)->getByIdAndType( | ||
| (string)$user->getId(), | ||
| AccessTokenService::TYPE_API_KEY | ||
| ); | ||
| if ($token && !$token->isRevoked()) { | ||
| $this->getDbService(AccessTokenService::class)->deleteEntity($token); | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| <?php | ||
|
|
||
| /** | ||
| * Database API key service factory | ||
| * | ||
| * PHP version 8 | ||
| * | ||
| * Copyright (C) The National Library of Finland 2025. | ||
| * | ||
| * 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 Database | ||
| * @author Juha Luoma <[email protected]> | ||
| * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License | ||
| * @link https://vufind.org/wiki/development:plugins:database_gateways Wiki | ||
| */ | ||
|
|
||
| namespace VuFind\ApiKey; | ||
|
|
||
| use Laminas\ServiceManager\Exception\ServiceNotCreatedException; | ||
| use Laminas\ServiceManager\Exception\ServiceNotFoundException; | ||
| use Laminas\ServiceManager\Factory\FactoryInterface; | ||
| use Psr\Container\ContainerExceptionInterface as ContainerException; | ||
| use Psr\Container\ContainerInterface; | ||
| use VuFind\Config\ConfigManager; | ||
demiankatz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * Database API key service factory | ||
| * | ||
| * @category VuFind | ||
| * @package Database | ||
| * @author Juha Luoma <[email protected]> | ||
| * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License | ||
| * @link https://vufind.org/wiki/development:plugins:database_gateways Wiki | ||
| */ | ||
| class ApiKeyServiceFactory implements FactoryInterface | ||
| { | ||
| /** | ||
| * Create an object | ||
| * | ||
| * @param ContainerInterface $container Service manager | ||
| * @param string $requestedName Service being created | ||
| * @param null|array $options Extra options (optional) | ||
| * | ||
| * @return object | ||
| * | ||
| * @throws ServiceNotFoundException if unable to resolve the service. | ||
| * @throws ServiceNotCreatedException if an exception is raised when | ||
| * creating a service. | ||
| * @throws ContainerException&\Throwable if any other error occurs | ||
| */ | ||
| public function __invoke( | ||
| ContainerInterface $container, | ||
| $requestedName, | ||
| ?array $options = null | ||
| ) { | ||
| if (!empty($options)) { | ||
| throw new \Exception('Unexpected options sent to factory!'); | ||
| } | ||
| return new $requestedName($container->get(ConfigManager::class)->getConfigArray('config/API_Keys')); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.