Skip to content

Commit 1619d5b

Browse files
committed
feat: add sensitive preferences_ex with encryption
Signed-off-by: Andrey Borysenko <andrey18106x@gmail.com>
1 parent f152c62 commit 1619d5b

4 files changed

Lines changed: 67 additions & 32 deletions

File tree

lib/Controller/PreferencesController.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ public function __construct(
4343
#[AppAPIAuth]
4444
#[PublicPage]
4545
#[NoCSRFRequired]
46-
public function setUserConfigValue(string $configKey, mixed $configValue): DataResponse {
46+
public function setUserConfigValue(string $configKey, mixed $configValue, ?int $sensitive = null): DataResponse {
4747
if ($configKey === '') {
4848
throw new OCSBadRequestException('Config key cannot be empty');
4949
}
5050
$userId = $this->userSession->getUser()->getUID();
5151
$appId = $this->request->getHeader('EX-APP-ID');
52-
$result = $this->exAppPreferenceService->setUserConfigValue($userId, $appId, $configKey, $configValue);
52+
$result = $this->exAppPreferenceService->setUserConfigValue($userId, $appId, $configKey, $configValue, $sensitive);
5353
if ($result instanceof ExAppPreference) {
5454
return new DataResponse($result, Http::STATUS_OK);
5555
}

lib/Db/ExAppPreference.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,19 @@
2121
* @method string getAppid()
2222
* @method string getConfigkey()
2323
* @method string getConfigvalue()
24+
* @method int getSensitive()
2425
* @method void setUserid(string $userid)
2526
* @method void setAppid(string $appid)
2627
* @method void setConfigkey(string $configkey)
2728
* @method void setConfigvalue(string $configvalue)
29+
* @method void setSensitive(int $sensitive)
2830
*/
2931
class ExAppPreference extends Entity implements JsonSerializable {
3032
protected $userid;
3133
protected $appid;
3234
protected $configkey;
3335
protected $configvalue;
36+
protected $sensitive;
3437

3538
/**
3639
* @param array $params
@@ -40,6 +43,7 @@ public function __construct(array $params = []) {
4043
$this->addType('appid', 'string');
4144
$this->addType('configkey', 'string');
4245
$this->addType('configvalue', 'string');
46+
$this->addType('sensitive', 'int');
4347

4448
if (isset($params['id'])) {
4549
$this->setId($params['id']);
@@ -56,6 +60,9 @@ public function __construct(array $params = []) {
5660
if (isset($params['configvalue'])) {
5761
$this->setConfigvalue($params['configvalue']);
5862
}
63+
if (isset($params['sensitive'])) {
64+
$this->setSensitive($params['sensitive']);
65+
}
5966
}
6067

6168
public function jsonSerialize(): array {
@@ -65,6 +72,7 @@ public function jsonSerialize(): array {
6572
'appid' => $this->getAppid(),
6673
'configkey' => $this->getConfigkey(),
6774
'configvalue' => $this->getConfigvalue(),
75+
'sensitive' => $this->getSensitive(),
6876
];
6977
}
7078
}

lib/Migration/Version032002Date20250527174907.php

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
use Closure;
1313
use OCP\DB\ISchemaWrapper;
14+
use OCP\DB\Types;
1415
use OCP\IDBConnection;
1516
use OCP\Migration\IOutput;
1617
use OCP\Migration\SimpleMigrationStep;
@@ -24,6 +25,30 @@ public function __construct(
2425
) {
2526
}
2627

28+
/**
29+
* @param IOutput $output
30+
* @param Closure(): ISchemaWrapper $schemaClosure
31+
* @param array $options
32+
* @return null|ISchemaWrapper
33+
*/
34+
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
35+
/** @var ISchemaWrapper $schema */
36+
$schema = $schemaClosure();
37+
38+
if ($schema->hasTable('preferences_ex')) {
39+
$table = $schema->getTable('preferences_ex');
40+
41+
if (!$table->hasColumn('sensitive')) {
42+
$table->addColumn('sensitive', Types::SMALLINT, [
43+
'notnull' => true,
44+
'default' => 0,
45+
]);
46+
}
47+
}
48+
49+
return $schema;
50+
}
51+
2752
/**
2853
* @param IOutput $output
2954
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
@@ -32,7 +57,7 @@ public function __construct(
3257
* @return null|ISchemaWrapper
3358
*/
3459
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) {
35-
// encrypt appconfig_ex and preferences_ex values that have sensitive flag set
60+
// encrypt appconfig_ex values that have sensitive flag set
3661

3762
$qbSelect = $this->connection->getQueryBuilder();
3863
$qbSelect->select(['id', 'configvalue'])
@@ -58,30 +83,6 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array
5883
}
5984
}
6085

61-
$qbSelect = $this->connection->getQueryBuilder();
62-
$qbSelect->select(['id', 'configvalue'])
63-
->from('preferences_ex')
64-
->where($qbSelect->expr()->eq('sensitive', $qbSelect->createNamedParameter(1)));
65-
$req = $qbSelect->executeQuery();
66-
67-
while ($row = $req->fetch()) {
68-
$configValue = $row['configvalue'];
69-
if (!empty($configValue)) {
70-
try {
71-
$encryptedValue = $this->crypto->encrypt($configValue);
72-
$qbUpdate = $this->connection->getQueryBuilder();
73-
$qbUpdate->update('preferences_ex')
74-
->set('configvalue', $qbUpdate->createNamedParameter($encryptedValue))
75-
->where(
76-
$qbUpdate->expr()->eq('id', $qbUpdate->createNamedParameter($row['id']))
77-
);
78-
$qbUpdate->executeStatement();
79-
} catch (\Exception $e) {
80-
$output->warning(sprintf('Failed to encrypt sensitive value for preference id %s: %s', $row['id'], $e->getMessage()));
81-
}
82-
}
83-
}
84-
8586
$req->closeCursor();
8687
return null;
8788
}

lib/Service/ExAppPreferenceService.php

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use OCP\AppFramework\Db\DoesNotExistException;
1616
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
1717
use OCP\DB\Exception;
18+
use OCP\Security\ICrypto;
1819
use Psr\Log\LoggerInterface;
1920

2021
/**
@@ -25,48 +26,73 @@ class ExAppPreferenceService {
2526
public function __construct(
2627
private ExAppPreferenceMapper $mapper,
2728
private LoggerInterface $logger,
29+
private ICrypto $crypto,
2830
) {
2931
}
3032

31-
public function setUserConfigValue(string $userId, string $appId, string $configKey, mixed $configValue) {
33+
public function setUserConfigValue(string $userId, string $appId, string $configKey, mixed $configValue, ?int $sensitive = null): ?ExAppPreference {
3234
try {
3335
$exAppPreference = $this->mapper->findByUserIdAppIdKey($userId, $appId, $configKey);
3436
} catch (DoesNotExistException|MultipleObjectsReturnedException|Exception) {
3537
$exAppPreference = null;
3638
}
39+
if ($sensitive) {
40+
try {
41+
$encryptedValue = $this->crypto->encrypt($configValue);
42+
} catch (\Exception $e) {
43+
$this->logger->error('Failed to encrypt sensitive value: ' . $e->getMessage(), ['exception' => $e]);
44+
return null;
45+
}
46+
}
3747
if ($exAppPreference === null) {
3848
try {
3949
return $this->mapper->insert(new ExAppPreference([
4050
'userid' => $userId,
4151
'appid' => $appId,
4252
'configkey' => $configKey,
43-
'configvalue' => $configValue ?? '',
53+
'configvalue' => $sensitive ? $encryptedValue ?? '' : $configValue ?? '',
54+
'sensitive' => $sensitive ? 1 : 0,
4455
]));
4556
} catch (Exception $e) {
4657
$this->logger->error('Error while inserting new config value: ' . $e->getMessage(), ['exception' => $e]);
4758
return null;
4859
}
4960
} else {
50-
$exAppPreference->setConfigvalue($configValue);
61+
$exAppPreference->setConfigvalue($sensitive ? $encryptedValue ?? '' : $configValue);
62+
if ($sensitive !== null) {
63+
$exAppPreference->setSensitive($sensitive);
64+
}
5165
try {
5266
if ($this->mapper->updateUserConfigValue($exAppPreference) !== 1) {
5367
$this->logger->error('Error while updating preferences_ex config value');
5468
return null;
5569
}
56-
return $exAppPreference;
5770
} catch (Exception $e) {
5871
$this->logger->error('Error while updating config value: ' . $e->getMessage(), ['exception' => $e]);
5972
return null;
6073
}
6174
}
75+
if ($sensitive) {
76+
$exAppPreference->setConfigvalue($configValue);
77+
}
78+
return $exAppPreference;
6279
}
6380

6481
public function getUserConfigValues(string $userId, string $appId, array $configKeys): ?array {
6582
try {
6683
return array_map(function (ExAppPreference $exAppPreference) {
84+
$value = $exAppPreference->getConfigvalue();
85+
if ($value !== '' && $exAppPreference->getSensitive()) {
86+
try {
87+
$value = $this->crypto->decrypt($value);
88+
} catch (\Exception $e) {
89+
$this->logger->warning(sprintf('Failed to decrypt sensitive value for user %s, app %s, config key %s', $exAppPreference->getUserid(), $exAppPreference->getAppid(), $exAppPreference->getConfigkey()), ['exception' => $e]);
90+
$value = '';
91+
}
92+
}
6793
return [
6894
'configkey' => $exAppPreference->getConfigkey(),
69-
'configvalue' => $exAppPreference->getConfigvalue() ?? '',
95+
'configvalue' => $value,
7096
];
7197
}, $this->mapper->findByUserIdAppIdKeys($userId, $appId, $configKeys));
7298
} catch (Exception) {

0 commit comments

Comments
 (0)