Skip to content

Commit d8b231b

Browse files
committed
Merge branch 'api_token_encryption_3_3_0-846' into 'stable-3_3_0'
Encrypt Dataverse API Token See merge request softwares-pkp/plugins_ojs/dataverse!212
2 parents 564d844 + 0eb7074 commit d8b231b

12 files changed

Lines changed: 185 additions & 3 deletions

File tree

.gitlab-ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ include:
99
- 'templates/groups/ops/unit_tests.yml'
1010
- 'templates/groups/ojs/unit_tests.yml'
1111
- 'templates/groups/ojs/cypress_tests.yml'
12+
13+
.integration_tests_template:
14+
before_script:
15+
- sed -i "s/api_key_secret = \"\"/api_key_secret = \"$API_KEY_SECRET\"/" /var/www/$CY_APPLICATION/config.inc.php

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ The latest release of this plugin is compatible with the following PKP applicati
1313

1414
Using PHP between 7.3 and 8.1.
1515

16+
## Requirements for usage
17+
18+
1. **api_key_secret**
19+
20+
The OJS/OPS instance must have the `api_key_secret` configuration set up, you may contact your system administrator to do that (see [this post](https://forum.pkp.sfu.ca/t/how-to-generate-a-api-key-secret-code-in-ojs-3/72008)).
21+
22+
This is required to use the API credentials provided, that are stored encrypted in the OJS/OPS database.
23+
1624
## Plugin Download
1725

1826
To download the plugin, go to the [Releases page](https://github.com/lepidus/dataversePlugin/releases) and download the tar.gz package of the latest release compatible with your website.

classes/DataEncryption.inc.php

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
use Illuminate\Encryption\Encrypter;
4+
5+
class DataEncryption
6+
{
7+
private const ENCRYPTION_CIPHER = 'AES-256-CBC';
8+
private const BASE64_PREFIX = 'base64:';
9+
10+
public function secretConfigExists(): bool
11+
{
12+
try {
13+
$this->getSecretFromConfig();
14+
} catch (Exception $e) {
15+
return false;
16+
}
17+
return true;
18+
}
19+
20+
private function getSecretFromConfig(): string
21+
{
22+
$secret = \Config::getVar('security', 'api_key_secret');
23+
if ($secret === "") {
24+
throw new Exception("Dataverse Error: A secret must be set in the config file ('api_key_secret') so that keys can be encrypted and decrypted");
25+
}
26+
27+
return $this->normalizeSecret($secret);
28+
}
29+
30+
private function normalizeSecret(string $secret): string
31+
{
32+
return hash('sha256', $secret, true);
33+
}
34+
35+
public function textIsEncrypted(string $text): bool
36+
{
37+
if (!str_starts_with($text, self::BASE64_PREFIX)) {
38+
return false;
39+
}
40+
41+
try {
42+
$this->decryptString($text);
43+
return true;
44+
} catch (Exception $e) {
45+
return false;
46+
}
47+
}
48+
49+
public function encryptString(string $plainText): string
50+
{
51+
$secret = $this->getSecretFromConfig();
52+
$encrypter = new Encrypter($secret, self::ENCRYPTION_CIPHER);
53+
54+
try {
55+
$encryptedString = $encrypter->encrypt($plainText);
56+
} catch (Exception $e) {
57+
throw new Exception("Dataverse Error: Failed to encrypt string");
58+
}
59+
60+
return self::BASE64_PREFIX . base64_encode($encryptedString);
61+
}
62+
63+
public function decryptString(string $encryptedText): string
64+
{
65+
$secret = $this->getSecretFromConfig();
66+
$encrypter = new Encrypter($secret, self::ENCRYPTION_CIPHER);
67+
68+
$encryptedText = str_replace(self::BASE64_PREFIX, '', $encryptedText);
69+
$payload = base64_decode($encryptedText);
70+
71+
try {
72+
$decryptedString = $encrypter->decrypt($payload);
73+
} catch (Exception $e) {
74+
throw new Exception("Dataverse Error: Failed to decrypt string");
75+
}
76+
77+
return $decryptedString;
78+
}
79+
}

classes/dataverseConfiguration/DataverseConfigurationDAO.inc.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
use Illuminate\Database\Capsule\Manager as Capsule;
44

5+
import('plugins.generic.dataverse.classes.DataEncryption');
56
import('plugins.generic.dataverse.classes.dataverseConfiguration.DataverseConfiguration');
67

78
class DataverseConfigurationDAO
@@ -35,6 +36,7 @@ public function hasConfiguration(int $contextId): bool
3536
public function get(int $contextId): DataverseConfiguration
3637
{
3738
$settings = $this->dao->getPluginSettings($contextId, $this->pluginName);
39+
$settings = $this->decryptApiToken($settings);
3840
$configuration = $this->newDataObject();
3941
$configuration->setAllData($settings);
4042
return $configuration;
@@ -52,4 +54,15 @@ public function insert(int $contextId, DataverseConfiguration $configuration): v
5254
);
5355
}
5456
}
57+
58+
private function decryptApiToken(array $settings): array
59+
{
60+
if (isset($settings['apiToken'])) {
61+
$encryption = new DataEncryption();
62+
if ($encryption->textIsEncrypted($settings['apiToken'])) {
63+
$settings['apiToken'] = $encryption->decryptString($settings['apiToken']);
64+
}
65+
}
66+
return $settings;
67+
}
5568
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Capsule\Manager as Capsule;
5+
6+
import('plugins.generic.dataverse.classes.DataEncryption');
7+
8+
class APITokenEncryptionMigration extends Migration
9+
{
10+
public function up(): void
11+
{
12+
$encrypter = new DataEncryption();
13+
if (!$encrypter->secretConfigExists()) {
14+
return;
15+
}
16+
17+
Capsule::table('plugin_settings')
18+
->where('plugin_name', 'dataverseplugin')
19+
->where('setting_name', 'apiToken')
20+
->get(['context_id', 'setting_value'])
21+
->each(function ($row) use ($encrypter) {
22+
if (empty($row->setting_value) || $encrypter->textIsEncrypted($row->setting_value)) {
23+
return;
24+
}
25+
26+
$encryptedValue = $encrypter->encryptString($row->setting_value);
27+
Capsule::table('plugin_settings')
28+
->where('plugin_name', 'dataverseplugin')
29+
->where('context_id', $row->context_id)
30+
->where('setting_name', 'apiToken')
31+
->update(['setting_value' => $encryptedValue]);
32+
});
33+
}
34+
}

locale/en_US/locale.po

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ msgstr "Dataverse Plugin"
1717
msgid "plugins.generic.dataverse.description"
1818
msgstr "Deposit data sets and/or other supplementary files to a Dataverse."
1919

20+
msgid "plugins.generic.dataverse.settings.emptyApiSecretKey"
21+
msgstr "The administrator must set a secret in the site configuration file ('api_key_secret')."
22+
2023
msgid "plugins.generic.dataverse.settings.description"
2124
msgstr ""
2225
"Configure the Dataverse API to deposit research data into a Dataverse repository.<br>"

locale/es_ES/locale.po

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ msgstr "Módulo Dataverse"
1717
msgid "plugins.generic.dataverse.description"
1818
msgstr "Deposita conjuntos de datos y/u otros documentos complementarios en Dataverse."
1919

20+
msgid "plugins.generic.dataverse.settings.emptyApiSecretKey"
21+
msgstr "Es necesario que el administrador establezca un secreto en el archivo de configuración del sitio ('api_key_secret')."
22+
2023
msgid "plugins.generic.dataverse.settings.description"
2124
msgstr ""
2225
"Configure la API Dataverse para depositar datos de investigación en un repositorio de Dataverse.<br>"

locale/pt_BR/locale.po

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ msgstr "Plugin Dataverse"
1717
msgid "plugins.generic.dataverse.description"
1818
msgstr "Deposita conjuntos de dados e/ou outros documentos suplementares no Dataverse."
1919

20+
msgid "plugins.generic.dataverse.settings.emptyApiSecretKey"
21+
msgstr "É necessário que o administrador defina um segredo no arquivo de configuração do site ('api_key_secret')."
22+
2023
msgid "plugins.generic.dataverse.settings.description"
2124
msgstr ""
2225
"Configure a API Dataverse para depositar dados de pesquisa em um repositório Dataverse.<br>"

settings/DataverseSettingsForm.inc.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
import('lib.pkp.classes.form.Form');
4+
import('plugins.generic.dataverse.classes.DataEncryption');
45
import('plugins.generic.dataverse.classes.dataverseConfiguration.DataverseConfigurationDAO');
56
import('plugins.generic.dataverse.dataverseAPI.actions.DataverseCollectionActions');
67
import('plugins.generic.dataverse.settings.DefaultAdditionalInstructions');
@@ -19,7 +20,9 @@ class DataverseSettingsForm extends Form
1920

2021
public function __construct(Plugin $plugin, int $contextId)
2122
{
22-
parent::__construct($plugin->getTemplateResource('dataverseConfigurationForm.tpl'));
23+
$encryption = new DataEncryption();
24+
$template = $encryption->secretConfigExists() ? 'dataverseConfigurationForm.tpl' : 'emptySecretKey.tpl';
25+
parent::__construct($plugin->getTemplateResource($template));
2326

2427
$this->plugin = $plugin;
2528
$this->contextId = $contextId;
@@ -92,6 +95,7 @@ public function fetch($request, $template = null, $display = false)
9295
public function execute(...$functionArgs)
9396
{
9497
$this->setDefaultAdditionalInstructions();
98+
$this->encryptApiToken();
9599
foreach (self::CONFIG_VARS as $configVar => $type) {
96100
$this->plugin->updateSetting($this->contextId, $configVar, $this->getData($configVar), $type);
97101
}
@@ -129,4 +133,15 @@ private function setDefaultAdditionalInstructions(): void
129133

130134
$this->setData('additionalInstructions', $additionalInstructions);
131135
}
136+
137+
private function encryptApiToken(): void
138+
{
139+
$encryption = new DataEncryption();
140+
$apiToken = $this->getData('apiToken');
141+
142+
if (!$encryption->textIsEncrypted($apiToken)) {
143+
$encryptedToken = $encryption->encryptString($apiToken);
144+
$this->setData('apiToken', $encryptedToken);
145+
}
146+
}
132147
}

templates/emptySecretKey.tpl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{**
2+
* templates/dataverseConfigurationForm.tpl
3+
*
4+
* Copyright (c) 2019-2025 Lepidus Tecnologia
5+
* Copyright (c) 2020-2025 SciELO
6+
* Distributed under the GNU GPL v3. For full terms see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt
7+
*
8+
* Dataverse plugin empty secret key warning template
9+
*
10+
*}
11+
12+
{translate key="plugins.generic.dataverse.settings.emptyApiSecretKey"}

0 commit comments

Comments
 (0)