diff --git a/classes/migration/upgrade/v3_4_0/I7366_UpdateUserAPIKeySettings.php b/classes/migration/upgrade/v3_4_0/I7366_UpdateUserAPIKeySettings.php new file mode 100644 index 00000000000..4bcce25698d --- /dev/null +++ b/classes/migration/upgrade/v3_4_0/I7366_UpdateUserAPIKeySettings.php @@ -0,0 +1,52 @@ +pluck('user_id') + ->chunk(1000) + ->each( + fn ($ids) => DB::table('user_settings') + ->where('setting_name', 'apiKeyEnabled') + ->whereIn('user_id', $ids->toArray()) + ->delete() + ); + } + + /** + * Reverse the migration. + */ + public function down(): void + { + + } +} diff --git a/classes/user/form/APIProfileForm.php b/classes/user/form/APIProfileForm.php index dae96341228..1b807d75b5e 100644 --- a/classes/user/form/APIProfileForm.php +++ b/classes/user/form/APIProfileForm.php @@ -17,15 +17,17 @@ use APP\core\Application; use APP\notification\NotificationManager; - use APP\template\TemplateManager; use Firebase\JWT\JWT; use PKP\config\Config; - use PKP\notification\PKPNotification; +use PKP\user\User; class APIProfileForm extends BaseProfileForm { + public const API_KEY_NEW = 1; + public const API_KEY_DELETE = 0; + /** * Constructor. * @@ -53,7 +55,9 @@ public function readInputData() parent::readInputData(); $this->readUserVars([ - 'apiKeyEnabled', 'generateApiKey', + 'apiKeyEnabled', + 'generateApiKey', + 'apiKeyAction', ]); } @@ -70,21 +74,23 @@ public function fetch($request, $template = null, $display = false) { $user = $request->getUser(); $secret = Config::getVar('security', 'api_key_secret', ''); + $templateMgr = TemplateManager::getManager($request); + if ($secret === '') { - $notificationManager = new NotificationManager(); - $notificationManager->createTrivialNotification( - $user->getId(), - PKPNotification::NOTIFICATION_TYPE_WARNING, - [ - 'contents' => __('user.apiKey.secretRequired'), - ] - ); - } elseif ($user->getData('apiKey')) { - $templateMgr = TemplateManager::getManager($request); - $templateMgr->assign([ - 'apiKey' => JWT::encode($user->getData('apiKey'), $secret, 'HS256'), - ]); + $this->handleOnMissingAPISecret($templateMgr, $user); + return parent::fetch($request, $template, $display); } + + $templateMgr->assign($user->getData('apiKey') ? [ + 'apiKey' => JWT::encode($user->getData('apiKey'), $secret, 'HS256'), + 'apiKeyAction' => self::API_KEY_DELETE, + 'apiKeyActionTextKey' => 'user.apiKey.remove', + ] : [ + 'apiKeyAction' => self::API_KEY_NEW, + 'apiKeyActionTextKey' => 'user.apiKey.generate', + ] + ); + return parent::fetch($request, $template, $display); } @@ -95,25 +101,45 @@ public function execute(...$functionArgs) { $request = Application::get()->getRequest(); $user = $request->getUser(); + $templateMgr = TemplateManager::getManager($request); - $apiKeyEnabled = (bool) $this->getData('apiKeyEnabled'); - $user->setData('apiKeyEnabled', $apiKeyEnabled); - - // remove api key if exists - if (!$apiKeyEnabled) { - $user->setData('apiKeyEnabled', null); + if (Config::getVar('security', 'api_key_secret', '') === '') { + $this->handleOnMissingAPISecret($templateMgr, $user); + parent::execute(...$functionArgs); } - // generate api key - if ($apiKeyEnabled && !is_null($this->getData('generateApiKey'))) { - $secret = Config::getVar('security', 'api_key_secret', ''); - if ($secret) { - $user->setData('apiKey', sha1(time())); - } - } + $apiKeyAction = (int)$this->getData('apiKeyAction'); + + $user->setData('apiKeyEnabled', $apiKeyAction === self::API_KEY_NEW ? 1 : null); + $user->setData('apiKey', $apiKeyAction === self::API_KEY_NEW ? sha1(time()) : null); + + $this->setData('apiKeyAction', (int)!$apiKeyAction); parent::execute(...$functionArgs); } + + /** + * Handle on missing API secret + * + * @param TemplateManager $templateMgr + * @param User $user + * + * @return void + */ + protected function handleOnMissingAPISecret(TemplateManager $templateMgr, User $user): void + { + $notificationManager = new NotificationManager(); + $notificationManager->createTrivialNotification( + $user->getId(), + PKPNotification::NOTIFICATION_TYPE_WARNING, + [ + 'contents' => __('user.apiKey.secretRequired'), + ] + ); + $templateMgr->assign([ + 'apiSecretMissing' => true, + ]); + } } if (!PKP_STRICT_MODE) { diff --git a/locale/en_US/user.po b/locale/en_US/user.po index cf11d72edbe..bd68a4648f3 100644 --- a/locale/en_US/user.po +++ b/locale/en_US/user.po @@ -426,11 +426,20 @@ msgid "user.apiKeyEnabled" msgstr "Enable external applications with the API key to access this account" msgid "user.apiKey.generate" -msgstr "Generate new API key" +msgstr "Create API Key" + +msgid "user.apiKey.remove" +msgstr "Delete" msgid "user.apiKey.generateWarning" msgstr "Generating a new API key will invalidate any existing key for this user." +msgid "user.apiKey.removeWarning" +msgstr "Deleting a key will revoke access to any application that uses it." + +msgid "user.apiKey.remove.confirmation.message" +msgstr "Are you sure you want to delete this API key?" + msgid "user.apiKey.secretRequired" msgstr "Before generating an API key, your site administrator must set a secret in the config file (\"api_key_secret\")." diff --git a/templates/user/apiProfileForm.tpl b/templates/user/apiProfileForm.tpl index a8ec55c77c2..e4d767ba2b5 100644 --- a/templates/user/apiProfileForm.tpl +++ b/templates/user/apiProfileForm.tpl @@ -23,21 +23,32 @@ {include file="controllers/notification/inPlaceNotification.tpl" notificationId="apiProfileNotification"} - {fbvFormSection list=true} - {fbvElement id=apiKeyEnabled type="checkbox" label="user.apiKeyEnabled" checked=$apiKeyEnabled value=1} - {fbvElement id=generateApiKey type="checkbox" label="user.apiKey.generate" value=1} - {/fbvFormSection} -

{translate key="user.apiKey.generateWarning"}

- - {fbvFormSection} + {fbvFormSection title="user.apiKey"} {if !$apiKey}{assign var=apiKey value="common.none"|translate}{/if} - {fbvElement id=apiKey type="text" label="user.apiKey" readonly="true" value=$apiKey size=$fbvStyles.size.MEDIUM} + {fbvElement id=apiKey type="text" readonly="true" inline=true value=$apiKey size=$fbvStyles.size.MEDIUM} + {if !$apiSecretMissing} + {fbvElement id=apiKeyAction type="hidden" readonly="true" value=$apiKeyAction} + + {/if} +

+ {translate key=($apiKeyAction === \PKP\user\form\APIProfileForm::API_KEY_NEW) ? "user.apiKey.generateWarning" : "user.apiKey.removeWarning"} +

{/fbvFormSection}

- {capture assign="privacyUrl"}{url router=\PKP\core\PKPApplication::ROUTE_PAGE page="about" op="privacy"}{/capture} + {capture assign="privacyUrl"} + {url router=\PKP\core\PKPApplication::ROUTE_PAGE page="about" op="privacy"} + {/capture} {translate key="user.privacyLink" privacyUrl=$privacyUrl}

- - {fbvFormButtons hideCancel=true submitText="common.save"}