Skip to content

Commit

Permalink
encrypt api keys and basic passwords for app config and user settings
Browse files Browse the repository at this point in the history
Signed-off-by: Julien Veyssier <[email protected]>
  • Loading branch information
julien-nc committed Oct 3, 2024
1 parent 71591e6 commit f6dbd42
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 13 deletions.
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Negative:
Learn more about the Nextcloud Ethical AI Rating [in our blog](https://nextcloud.com/blog/nextcloud-ethical-ai-rating/).
]]> </description>
<version>3.1.2</version>
<version>3.1.3</version>
<licence>agpl</licence>
<author>Julien Veyssier</author>
<namespace>OpenAi</namespace>
Expand Down
89 changes: 89 additions & 0 deletions lib/Migration/Version030102Date20241003155512.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\OpenAi\Migration;

use Closure;
use OCA\OpenAi\AppInfo\Application;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
use OCP\Security\ICrypto;

class Version030102Date20241003155512 extends SimpleMigrationStep {

public function __construct(
private IDBConnection $connection,
private ICrypto $crypto,
private IConfig $config,
) {
}

/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
*/
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) {
// app config
foreach (['api_key', 'basic_password'] as $key) {
$value = $this->config->getAppValue(Application::APP_ID, $key);
if ($value !== '') {
$encryptedValue = $this->crypto->encrypt($value);
$this->config->setAppValue(Application::APP_ID, $key, $encryptedValue);
}
}

// user api keys and passwords
$qbUpdate = $this->connection->getQueryBuilder();
$qbUpdate->update('preferences')
->set('configvalue', $qbUpdate->createParameter('updateValue'))
->where(
$qbUpdate->expr()->eq('appid', $qbUpdate->createNamedParameter(Application::APP_ID, IQueryBuilder::PARAM_STR))
)
->andWhere(
$qbUpdate->expr()->eq('userid', $qbUpdate->createParameter('updateUserId'))
)
->andWhere(
$qbUpdate->expr()->eq('configkey', $qbUpdate->createParameter('updateConfigKey'))
);

$qbSelect = $this->connection->getQueryBuilder();
$qbSelect->select('userid', 'configvalue', 'configkey')
->from('preferences')
->where(
$qbSelect->expr()->eq('appid', $qbSelect->createNamedParameter(Application::APP_ID, IQueryBuilder::PARAM_STR))
);

$or = $qbSelect->expr()->orx();
$or->add($qbSelect->expr()->eq('configkey', $qbSelect->createNamedParameter('api_key', IQueryBuilder::PARAM_STR)));
$or->add($qbSelect->expr()->eq('configkey', $qbSelect->createNamedParameter('basic_password', IQueryBuilder::PARAM_STR)));
$qbSelect->andWhere($or);

$qbSelect->andWhere(
$qbSelect->expr()->nonEmptyString('configvalue')
)
->andWhere(
$qbSelect->expr()->isNotNull('configvalue')
);
$req = $qbSelect->executeQuery();
while ($row = $req->fetch()) {
$userId = $row['userid'];
$configKey = $row['configkey'];
$storedClearValue = $row['configvalue'];
$encryptedValue = $this->crypto->encrypt($storedClearValue);
$qbUpdate->setParameter('updateConfigKey', $configKey, IQueryBuilder::PARAM_STR);
$qbUpdate->setParameter('updateValue', $encryptedValue, IQueryBuilder::PARAM_STR);
$qbUpdate->setParameter('updateUserId', $userId, IQueryBuilder::PARAM_STR);
$qbUpdate->executeStatement();
}
$req->closeCursor();
}
}
58 changes: 46 additions & 12 deletions lib/Service/OpenAiSettingsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
use Exception;
use OCA\OpenAi\AppInfo\Application;
use OCP\IConfig;
use OCP\PreConditionNotMetException;
use OCP\Security\ICrypto;

class OpenAiSettingsService {
private const ADMIN_CONFIG_TYPES = [
Expand Down Expand Up @@ -57,7 +59,10 @@ class OpenAiSettingsService {
];


public function __construct(private IConfig $config) {
public function __construct(
private IConfig $config,
private ICrypto $crypto,
) {

}

Expand All @@ -66,21 +71,28 @@ public function __construct(private IConfig $config) {

/**
* @return string
* @throws Exception
*/
public function getAdminApiKey(): string {
return $this->config->getAppValue(Application::APP_ID, 'api_key');
$encryptedApiKey = $this->config->getAppValue(Application::APP_ID, 'api_key');
return $encryptedApiKey === '' ? '' : $this->crypto->decrypt($encryptedApiKey);
}

/**
* SIC! Does not fall back on the admin api by default
* @param null|string $userId
* @param boolean $fallBackOnAdminValue
* @return string
* @throws Exception
*/
public function getUserApiKey(?string $userId, bool $fallBackOnAdminValue = false): string {
$fallBackApiKey = $fallBackOnAdminValue ? $this->getAdminApiKey() : '';
$userApiKey = $userId === null ? $fallBackApiKey : ($this->config->getUserValue($userId, Application::APP_ID, 'api_key', $fallBackApiKey) ?: $fallBackApiKey);
return $userApiKey;
if ($userId === null) {
return $fallBackApiKey;
}
$encryptedUserApiKey = $this->config->getUserValue($userId, Application::APP_ID, 'api_key');
$userApiKey = $encryptedUserApiKey === '' ? '' : $this->crypto->decrypt($encryptedUserApiKey);
return $userApiKey ?: $fallBackApiKey;
}

/**
Expand Down Expand Up @@ -186,11 +198,16 @@ public function getUserBasicUser(?string $userId, bool $fallBackOnAdminValue = t
* @param string|null $userId
* @param bool $fallBackOnAdminValue
* @return string
* @throws Exception
*/
public function getUserBasicPassword(?string $userId, bool $fallBackOnAdminValue = true): string {
$fallBackBasicPassword = $fallBackOnAdminValue ? $this->config->getAppValue(Application::APP_ID, 'basic_password', '') : '';
$basicPassword = $userId === null ? $fallBackBasicPassword : ($this->config->getUserValue($userId, Application::APP_ID, 'basic_password', $fallBackBasicPassword) ?: $fallBackBasicPassword);
return $basicPassword;
$fallBackBasicPassword = $fallBackOnAdminValue ? $this->getAdminBasicPassword() : '';
if ($userId === null) {
return $fallBackBasicPassword;
}
$encryptedUserBasicPassword = $this->config->getUserValue($userId, Application::APP_ID, 'basic_password');
$userBasicPassword = $encryptedUserBasicPassword === '' ? '' : $this->crypto->decrypt($encryptedUserBasicPassword);
return $userBasicPassword ?: $fallBackBasicPassword;
}

/**
Expand All @@ -204,9 +221,11 @@ public function getAdminBasicUser(): string {
/**
* Get admin basic password
* @return string
* @throws Exception
*/
public function getAdminBasicPassword(): string {
return $this->config->getAppValue(Application::APP_ID, 'basic_password', '');
$encryptedBasicPassword = $this->config->getAppValue(Application::APP_ID, 'basic_password', '');
return $encryptedBasicPassword === '' ? '' : $this->crypto->decrypt($encryptedBasicPassword);
}

/**
Expand Down Expand Up @@ -324,16 +343,27 @@ public function setQuotas(array $quotas): void {
*/
public function setAdminApiKey(string $apiKey): void {
// No need to validate. As long as it's a string, we're happy campers
$this->config->setAppValue(Application::APP_ID, 'api_key', $apiKey);
if ($apiKey === '') {
$this->config->setAppValue(Application::APP_ID, 'api_key', '');
} else {
$encryptedApiKey = $this->crypto->encrypt($apiKey);
$this->config->setAppValue(Application::APP_ID, 'api_key', $encryptedApiKey);
}
}

/**
* @param string $userId
* @param string $apiKey
* @throws PreConditionNotMetException
*/
public function setUserApiKey(string $userId, string $apiKey): void {
// No need to validate. As long as it's a string, we're happy campers
$this->config->setUserValue($userId, Application::APP_ID, 'api_key', $apiKey);
if ($apiKey === '') {
$this->config->setUserValue($userId, Application::APP_ID, 'api_key', '');
} else {
$encryptedApiKey = $this->crypto->encrypt($apiKey);
$this->config->setUserValue($userId, Application::APP_ID, 'api_key', $encryptedApiKey);
}
}

/**
Expand Down Expand Up @@ -443,13 +473,15 @@ public function setAdminBasicUser(string $basicUser): void {
* @return void
*/
public function setAdminBasicPassword(string $basicPassword): void {
$this->config->setAppValue(Application::APP_ID, 'basic_password', $basicPassword);
$encryptedBasicPassword = $basicPassword === '' ? '' : $this->crypto->encrypt($basicPassword);
$this->config->setAppValue(Application::APP_ID, 'basic_password', $encryptedBasicPassword);
}

/**
* @param string $userId
* @param string $basicUser
* @return void
* @throws PreConditionNotMetException
*/
public function setUserBasicUser(string $userId, string $basicUser): void {
$this->config->setUserValue($userId, Application::APP_ID, 'basic_user', $basicUser);
Expand All @@ -459,9 +491,11 @@ public function setUserBasicUser(string $userId, string $basicUser): void {
* @param string $userId
* @param string $basicPassword
* @return void
* @throws PreConditionNotMetException
*/
public function setUserBasicPassword(string $userId, string $basicPassword): void {
$this->config->setUserValue($userId, Application::APP_ID, 'basic_password', $basicPassword);
$encryptedBasicPassword = $basicPassword === '' ? '' : $this->crypto->encrypt($basicPassword);
$this->config->setUserValue($userId, Application::APP_ID, 'basic_password', $encryptedBasicPassword);
}

/**
Expand Down

0 comments on commit f6dbd42

Please sign in to comment.