diff --git a/appinfo/info.xml b/appinfo/info.xml index b67271e6..d5c3840d 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -4,7 +4,7 @@ Google integration Import Google data into Nextcloud - 4.2.0 + 4.3.0 agpl Julien Veyssier Google @@ -16,7 +16,7 @@ https://github.com/nextcloud/integration_google/issues https://github.com/nextcloud/integration_google/raw/master/img/screenshot1.jpg - + OCA\Google\Settings\Admin diff --git a/lib/Controller/ConfigController.php b/lib/Controller/ConfigController.php index b31f00df..a26ebf91 100644 --- a/lib/Controller/ConfigController.php +++ b/lib/Controller/ConfigController.php @@ -23,10 +23,11 @@ use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Services\IAppConfig; use OCP\AppFramework\Services\IInitialState; +use OCP\Config\IUserConfig; use OCP\Constants; use OCP\Contacts\IManager as IContactManager; -use OCP\IConfig; use OCP\IL10N; use OCP\IRequest; use OCP\IURLGenerator; @@ -44,7 +45,8 @@ class ConfigController extends Controller { public function __construct( string $appName, IRequest $request, - private IConfig $config, + private IAppConfig $appConfig, + private IUserConfig $userConfig, private IURLGenerator $urlGenerator, private IL10N $l, private IContactManager $contactsManager, @@ -70,16 +72,16 @@ public function setConfig(array $values): DataResponse { return new DataResponse([], Http::STATUS_BAD_REQUEST); } foreach ($values as $key => $value) { - $this->config->setUserValue($this->userId, Application::APP_ID, $key, $value); + $this->userConfig->setValueString($this->userId, Application::APP_ID, $key, $value, lazy: true); } $result = []; if (isset($values['user_name']) && $values['user_name'] === '') { - $this->config->deleteUserValue($this->userId, Application::APP_ID, 'user_id'); - $this->config->deleteUserValue($this->userId, Application::APP_ID, 'user_name'); - $this->config->deleteUserValue($this->userId, Application::APP_ID, 'refresh_token'); - $this->config->deleteUserValue($this->userId, Application::APP_ID, 'token_expires_at'); - $this->config->deleteUserValue($this->userId, Application::APP_ID, 'token'); + $this->userConfig->deleteUserConfig($this->userId, Application::APP_ID, 'user_id'); + $this->userConfig->deleteUserConfig($this->userId, Application::APP_ID, 'user_name'); + $this->userConfig->deleteUserConfig($this->userId, Application::APP_ID, 'refresh_token'); + $this->userConfig->deleteUserConfig($this->userId, Application::APP_ID, 'token_expires_at'); + $this->userConfig->deleteUserConfig($this->userId, Application::APP_ID, 'token'); $result['user_name'] = ''; } else { if (isset($values['drive_output_dir'])) { @@ -106,10 +108,8 @@ public function setAdminConfig(array $values): DataResponse { if ($key === 'client_secret' && $value === 'dummySecret') { continue; } - if (in_array($key, ['client_secret', 'client_id'], true) && $value !== '') { - $value = $this->crypto->encrypt($value); - } - $this->config->setAppValue(Application::APP_ID, $key, $value); + $sensitive = in_array($key, ['client_secret', 'client_id'], true); + $this->appConfig->setAppValueString($key, $value, lazy: true, sensitive: $sensitive); } return new DataResponse(1); } @@ -172,9 +172,9 @@ public function oauthRedirect(string $code = '', string $state = '', string $sco ); } - $configState = $this->config->getUserValue($this->userId, Application::APP_ID, 'oauth_state'); - $clientID = $this->secretService->getEncryptedAppValue('client_id'); - $clientSecret = $this->secretService->getEncryptedAppValue('client_secret'); + $configState = $this->userConfig->getValueString($this->userId, Application::APP_ID, 'oauth_state', lazy: true); + $clientID = $this->appConfig->getAppValueString('client_id', lazy: true); + $clientSecret = $this->appConfig->getAppValueString('client_secret', lazy: true); // Store given scopes in space-separated string $scopes = explode(' ', $scope); @@ -186,13 +186,13 @@ public function oauthRedirect(string $code = '', string $state = '', string $sco 'can_access_calendar' => (in_array(self::CALENDAR_SCOPE, $scopes) && in_array(self::CALENDAR_EVENTS_SCOPE, $scopes)) ? 1 : 0, ]; - $this->config->setUserValue($this->userId, Application::APP_ID, 'user_scopes', json_encode($scopesArray)); + $this->userConfig->setValueString($this->userId, Application::APP_ID, 'user_scopes', json_encode($scopesArray), lazy: true); // anyway, reset state - $this->config->setUserValue($this->userId, Application::APP_ID, 'oauth_state', ''); + $this->userConfig->setValueString($this->userId, Application::APP_ID, 'oauth_state', '', lazy: true); if ($clientID && $clientSecret && $configState !== '' && $configState === $state) { - $redirect_uri = $this->config->getUserValue($this->userId, Application::APP_ID, 'redirect_uri'); + $redirect_uri = $this->userConfig->getValueString($this->userId, Application::APP_ID, 'redirect_uri', lazy: true); /** @var array{access_token?:string, refresh_token?:string, expires_in?:string, error?:string} $result */ $result = $this->googleApiService->requestOAuthAccessToken([ 'client_id' => $clientID, @@ -207,12 +207,12 @@ public function oauthRedirect(string $code = '', string $state = '', string $sco if (isset($result['expires_in'])) { $nowTs = (new DateTime())->getTimestamp(); $expiresAt = $nowTs + (int)$result['expires_in']; - $this->config->setUserValue($this->userId, Application::APP_ID, 'token_expires_at', (string)$expiresAt); + $this->userConfig->setValueInt($this->userId, Application::APP_ID, 'token_expires_at', $expiresAt, lazy: true); } $this->secretService->setEncryptedUserValue($this->userId, 'token', $accessToken); $this->secretService->setEncryptedUserValue($this->userId, 'refresh_token', $refreshToken); $username = $this->storeUserInfo(); - $usePopup = $this->config->getAppValue(Application::APP_ID, 'use_popup', '0') === '1'; + $usePopup = $this->appConfig->getAppValueString('use_popup', '0', lazy: true) === '1'; if ($usePopup) { return new RedirectResponse( $this->urlGenerator->linkToRoute('integration_google.config.popupSuccessPage', ['username' => $username]) @@ -248,12 +248,12 @@ private function storeUserInfo(): string { /** @var array{id?:string, name?:string} $info */ $info = $this->googleApiService->request($this->userId, 'oauth2/v1/userinfo', ['alt' => 'json']); if (isset($info['name'], $info['id'])) { - $this->config->setUserValue($this->userId, Application::APP_ID, 'user_id', $info['id']); - $this->config->setUserValue($this->userId, Application::APP_ID, 'user_name', $info['name']); + $this->userConfig->setValueString($this->userId, Application::APP_ID, 'user_id', $info['id'], lazy: true); + $this->userConfig->setValueString($this->userId, Application::APP_ID, 'user_name', $info['name'], lazy: true); return $info['name']; } else { - $this->config->setUserValue($this->userId, Application::APP_ID, 'user_id', ''); - $this->config->setUserValue($this->userId, Application::APP_ID, 'user_name', ''); + $this->userConfig->setValueString($this->userId, Application::APP_ID, 'user_id', '', lazy: true); + $this->userConfig->setValueString($this->userId, Application::APP_ID, 'user_name', '', lazy: true); return ''; } } diff --git a/lib/Controller/GoogleAPIController.php b/lib/Controller/GoogleAPIController.php index b99bb785..1cf88d50 100644 --- a/lib/Controller/GoogleAPIController.php +++ b/lib/Controller/GoogleAPIController.php @@ -19,7 +19,7 @@ use OCA\Google\Service\SecretService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\DataResponse; -use OCP\IConfig; +use OCP\Config\IUserConfig; use OCP\IRequest; class GoogleAPIController extends Controller { @@ -29,7 +29,7 @@ class GoogleAPIController extends Controller { public function __construct( string $appName, IRequest $request, - private IConfig $config, + private IUserConfig $userConfig, private GoogleContactsAPIService $googleContactsAPIService, private GoogleDriveAPIService $googleDriveAPIService, private GoogleCalendarAPIService $googleCalendarAPIService, @@ -50,10 +50,10 @@ public function getImportDriveInformation(): DataResponse { return new DataResponse([], 400); } return new DataResponse([ - 'importing_drive' => $this->config->getUserValue($this->userId, Application::APP_ID, 'importing_drive') === '1', - 'last_drive_import_timestamp' => (int)$this->config->getUserValue($this->userId, Application::APP_ID, 'last_drive_import_timestamp', '0'), - 'nb_imported_files' => (int)$this->config->getUserValue($this->userId, Application::APP_ID, 'nb_imported_files', '0'), - 'drive_imported_size' => (int)$this->config->getUserValue($this->userId, Application::APP_ID, 'drive_imported_size', '0'), + 'importing_drive' => $this->userConfig->getValueString($this->userId, Application::APP_ID, 'importing_drive', lazy: true) === '1', + 'last_drive_import_timestamp' => $this->userConfig->getValueInt($this->userId, Application::APP_ID, 'last_drive_import_timestamp', lazy: true), + 'nb_imported_files' => $this->userConfig->getValueInt($this->userId, Application::APP_ID, 'nb_imported_files', lazy: true), + 'drive_imported_size' => $this->userConfig->getValueInt($this->userId, Application::APP_ID, 'drive_imported_size', lazy: true), ]); } diff --git a/lib/Migration/Version03001001Date20241111105515.php b/lib/Migration/Version03001001Date20241111105515.php index 11679bcd..cc8130c0 100644 --- a/lib/Migration/Version03001001Date20241111105515.php +++ b/lib/Migration/Version03001001Date20241111105515.php @@ -10,8 +10,8 @@ use Closure; use OCA\Google\AppInfo\Application; +use OCP\AppFramework\Services\IAppConfig; use OCP\DB\QueryBuilder\IQueryBuilder; -use OCP\IConfig; use OCP\IDBConnection; use OCP\Migration\IOutput; use OCP\Migration\SimpleMigrationStep; @@ -20,7 +20,7 @@ class Version03001001Date20241111105515 extends SimpleMigrationStep { public function __construct( - private IConfig $config, + private IAppConfig $appConfig, private IDBConnection $connection, private ICrypto $crypto, ) { @@ -34,11 +34,11 @@ public function __construct( public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) { // migrate api credentials in app config foreach (['client_id', 'client_secret'] as $key) { - $value = $this->config->getAppValue(Application::APP_ID, $key); + $value = $this->appConfig->getAppValueString($key, ''); if ($value === '') { continue; } - $this->config->setAppValue(Application::APP_ID, $key, $this->crypto->encrypt($value)); + $this->appConfig->setAppValueString($key, $this->crypto->encrypt($value)); } // user tokens diff --git a/lib/Migration/Version04003001Date20251022113940.php b/lib/Migration/Version04003001Date20251022113940.php new file mode 100644 index 00000000..8e918888 --- /dev/null +++ b/lib/Migration/Version04003001Date20251022113940.php @@ -0,0 +1,41 @@ +appConfig->getAppValueString($key, lazy: true); + if ($value === '') { + continue; + } + $value = $this->crypto->decrypt($value); + $this->appConfig->setAppValueString($key, $value, lazy: true, sensitive: true); + } + } +} diff --git a/lib/Service/GoogleAPIService.php b/lib/Service/GoogleAPIService.php index 99bff192..ee2b6cc8 100644 --- a/lib/Service/GoogleAPIService.php +++ b/lib/Service/GoogleAPIService.php @@ -18,9 +18,10 @@ use GuzzleHttp\Exception\ConnectException; use GuzzleHttp\Exception\ServerException; use OCA\Google\AppInfo\Application; +use OCP\AppFramework\Services\IAppConfig; +use OCP\Config\IUserConfig; use OCP\Http\Client\IClientService; use OCP\Http\Client\IResponse; -use OCP\IConfig; use OCP\IL10N; use OCP\Notification\IManager as INotificationManager; use Psr\Log\LoggerInterface; @@ -37,7 +38,8 @@ public function __construct( string $appName, private LoggerInterface $logger, private IL10N $l10n, - private IConfig $config, + private IAppConfig $appConfig, + private IUserConfig $userConfig, private INotificationManager $notificationManager, IClientService $clientService, private SecretService $secretService, @@ -353,10 +355,9 @@ public function simpleDownload(string $userId, string $url, $resource, array $pa private function checkTokenExpiration(string $userId): void { $refreshToken = $this->secretService->getEncryptedUserValue($userId, 'refresh_token'); - $expireAt = $this->config->getUserValue($userId, Application::APP_ID, 'token_expires_at'); - if ($refreshToken !== '' && $expireAt !== '') { + $expireAt = $this->userConfig->getValueInt($userId, Application::APP_ID, 'token_expires_at', lazy: true); + if ($refreshToken !== '' && $expireAt !== 0) { $nowTs = (new DateTime())->getTimestamp(); - $expireAt = (int)$expireAt; // if token expires in less than 2 minutes or has already expired if ($nowTs > $expireAt - 120) { $this->refreshToken($userId); @@ -367,8 +368,8 @@ private function checkTokenExpiration(string $userId): void { public function refreshToken(string $userId): array { $this->logger->debug('Trying to REFRESH the access token', ['app' => Application::APP_ID]); $refreshToken = $this->secretService->getEncryptedUserValue($userId, 'refresh_token'); - $clientID = $this->secretService->getEncryptedAppValue('client_id'); - $clientSecret = $this->secretService->getEncryptedAppValue('client_secret'); + $clientID = $this->appConfig->getAppValueString('client_id', lazy: true); + $clientSecret = $this->appConfig->getAppValueString('client_secret', lazy: true); $result = $this->requestOAuthAccessToken([ 'client_id' => $clientID, 'client_secret' => $clientSecret, @@ -382,7 +383,7 @@ public function refreshToken(string $userId): array { if (isset($result['expires_in'])) { $nowTs = (new DateTime())->getTimestamp(); $expiresAt = $nowTs + (int)$result['expires_in']; - $this->config->setUserValue($userId, Application::APP_ID, 'token_expires_at', $expiresAt); + $this->userConfig->setValueInt($userId, Application::APP_ID, 'token_expires_at', $expiresAt, lazy: true); } } else { $responseTxt = json_encode($result); diff --git a/lib/Service/GoogleCalendarAPIService.php b/lib/Service/GoogleCalendarAPIService.php index 87a781b5..e56b2056 100644 --- a/lib/Service/GoogleCalendarAPIService.php +++ b/lib/Service/GoogleCalendarAPIService.php @@ -18,7 +18,7 @@ use Generator; use OCA\DAV\CalDAV\CalDavBackend; use OCA\Google\AppInfo\Application; -use OCP\IConfig; +use OCP\Config\IUserConfig; use OCP\IL10N; use Ortic\ColorConverter\Color; use Ortic\ColorConverter\Colors\Named; @@ -45,7 +45,7 @@ public function __construct( private IL10N $l10n, private CalDavBackend $caldavBackend, private GoogleAPIService $googleApiService, - private IConfig $config, + private IUserConfig $userConfig, ) { $this->utcTimezone = new DateTimeZone('-0000'); } @@ -330,7 +330,7 @@ public function importCalendar(string $userId, string $calId, string $calName, ? } date_default_timezone_set('UTC'); - $allEvents = $this->config->getUserValue($userId, Application::APP_ID, 'consider_all_events', '1') === '1'; + $allEvents = $this->userConfig->getValueString($userId, Application::APP_ID, 'consider_all_events', '1', lazy: true) === '1'; $eventsGenerator = $this->getCalendarEvents($userId, $calId, $allEvents); // Normal events diff --git a/lib/Service/GoogleContactsAPIService.php b/lib/Service/GoogleContactsAPIService.php index aaba6d85..dbac7824 100644 --- a/lib/Service/GoogleContactsAPIService.php +++ b/lib/Service/GoogleContactsAPIService.php @@ -17,8 +17,8 @@ use Generator; use OCA\DAV\CardDAV\CardDavBackend; use OCA\Google\AppInfo\Application; +use OCP\Config\IUserConfig; use OCP\Contacts\IManager as IContactManager; -use OCP\IConfig; use Psr\Log\LoggerInterface; use Sabre\VObject\Component\VCard; use Throwable; @@ -34,7 +34,7 @@ public function __construct( private IContactManager $contactsManager, private CardDavBackend $cdBackend, private GoogleAPIService $googleApiService, - private IConfig $config, + private IUserConfig $userConfig, ) { } @@ -85,7 +85,7 @@ public function getContactNumber(string $userId): array { return $contacts; } $result['nbContacts'] = $contacts['totalItems'] ?? 0; - $scopes = $this->config->getUserValue($userId, Application::APP_ID, 'user_scopes', '{}'); + $scopes = $this->userConfig->getValueString($userId, Application::APP_ID, 'user_scopes', '{}', lazy: true); $scopes = json_decode($scopes, true); if (isset($scopes['can_access_other_contacts']) && $scopes['can_access_other_contacts'] === 1) { $params = [ @@ -205,7 +205,7 @@ public function importContacts(string $userId, ?string $uri, int $key, ?string $ } $existingAddressBook = $addressBook; } - $otherContacts = $this->config->getUserValue($userId, Application::APP_ID, 'consider_other_contacts', '0') === '1'; + $otherContacts = $this->userConfig->getValueString($userId, Application::APP_ID, 'consider_other_contacts', '0', lazy: true) === '1'; $groupsById = $this->getContactGroupsById($userId); $contacts = $this->getContactList($userId, $otherContacts); $nbAdded = 0; diff --git a/lib/Service/GoogleDriveAPIService.php b/lib/Service/GoogleDriveAPIService.php index a2cd341a..30aacf34 100644 --- a/lib/Service/GoogleDriveAPIService.php +++ b/lib/Service/GoogleDriveAPIService.php @@ -25,7 +25,6 @@ use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; -use OCP\IConfig; use OCP\Lock\ILockingProvider; use OCP\Lock\LockedException; use OCP\PreConditionNotMetException; @@ -48,7 +47,6 @@ class GoogleDriveAPIService { public function __construct( string $appName, private LoggerInterface $logger, - private IConfig $config, private IUserConfig $userConfig, private IRootFolder $root, private IJobList $jobList, @@ -63,7 +61,7 @@ public function __construct( * @return array */ public function getDriveSize(string $userId): array { - $considerSharedFiles = $this->config->getUserValue($userId, Application::APP_ID, 'consider_shared_files', '0') === '1'; + $considerSharedFiles = $this->userConfig->getValueString($userId, APPLICATION::APP_ID, 'consider_shared_files', '0', lazy:true) === '1'; $params = [ 'fields' => '*', ]; @@ -113,13 +111,13 @@ public function getDriveSize(string $userId): array { * @throws PreConditionNotMetException */ public function startImportDrive(string $userId): array { - $targetPath = $this->config->getUserValue($userId, Application::APP_ID, 'drive_output_dir', '/Google Drive'); + $targetPath = $this->userConfig->getValueString($userId, APPLICATION::APP_ID, 'drive_output_dir', '/Google Drive', lazy:true); $targetPath = $targetPath ?: '/Google Drive'; - $considerSharedFiles = $this->config->getUserValue($userId, Application::APP_ID, 'consider_shared_files', '0') === '1'; - $targetSharedPath = $this->config->getUserValue($userId, Application::APP_ID, 'drive_shared_with_me_output_dir', '/Google Drive/Shared with me'); + $considerSharedFiles = $this->userConfig->getValueString($userId, APPLICATION::APP_ID, 'consider_shared_files', '0', lazy:true) === '1'; + $targetSharedPath = $this->userConfig->getValueString($userId, APPLICATION::APP_ID, 'drive_shared_with_me_output_dir', '/Google Drive/Shared with me', lazy:true); $targetSharedPath = $targetSharedPath ?: '/Google Drive/Shared with me'; - $alreadyImporting = $this->config->getUserValue($userId, Application::APP_ID, 'importing_drive', '0') === '1'; + $alreadyImporting = $this->userConfig->getValueString($userId, APPLICATION::APP_ID, 'importing_drive', '0', lazy:true) === '1'; if ($alreadyImporting) { return ['targetPath' => $targetPath]; } @@ -145,11 +143,11 @@ public function startImportDrive(string $userId): array { } } - $this->config->setUserValue($userId, Application::APP_ID, 'importing_drive', '1'); - $this->config->setUserValue($userId, Application::APP_ID, 'nb_imported_files', '0'); - $this->config->setUserValue($userId, Application::APP_ID, 'drive_imported_size', '0'); - $this->config->setUserValue($userId, Application::APP_ID, 'last_drive_import_timestamp', '0'); - $this->config->deleteUserValue($userId, Application::APP_ID, 'directory_progress'); + $this->userConfig->setValueString($userId, Application::APP_ID, 'importing_drive', '1', lazy: true); + $this->userConfig->setValueInt($userId, Application::APP_ID, 'nb_imported_files', 0, lazy: true); + $this->userConfig->setValueInt($userId, Application::APP_ID, 'drive_imported_size', 0, lazy: true); + $this->userConfig->setValueInt($userId, Application::APP_ID, 'last_drive_import_timestamp', 0, lazy: true); + $this->userConfig->deleteUserConfig($userId, Application::APP_ID, 'directory_progress'); $this->jobList->add(ImportDriveJob::class, ['user_id' => $userId]); return ['targetPath' => $targetPath]; @@ -171,28 +169,28 @@ public function importDriveJob(string $userId): void { $this->userScopeService->setUserScope($userId); $this->userScopeService->setFilesystemScope($userId); - $importingDrive = $this->config->getUserValue($userId, Application::APP_ID, 'importing_drive', '0') === '1'; + $importingDrive = $this->userConfig->getValueString($userId, APPLICATION::APP_ID, 'importing_drive', '0', lazy:true) === '1'; if (!$importingDrive) { return; } - $jobRunning = $this->config->getUserValue($userId, Application::APP_ID, 'drive_import_running', '0') === '1'; + $jobRunning = $this->userConfig->getValueString($userId, APPLICATION::APP_ID, 'drive_import_running', '0', lazy:true) === '1'; $nowTs = (new DateTime())->getTimestamp(); if ($jobRunning) { - $lastJobStart = $this->config->getUserValue($userId, Application::APP_ID, 'drive_import_job_last_start'); - if ($lastJobStart !== '' && ($nowTs - intval($lastJobStart) < Application::IMPORT_JOB_TIMEOUT)) { + $lastJobStart = $this->userConfig->getValueInt($userId, APPLICATION::APP_ID, 'drive_import_job_last_start', lazy:true); + if ($lastJobStart !== 0 && ($nowTs - intval($lastJobStart) < Application::IMPORT_JOB_TIMEOUT)) { $this->logger->info('Last job execution (' . strval($nowTs - intval($lastJobStart)) . ') is less than ' . strval(Application::IMPORT_JOB_TIMEOUT) . ' seconds ago, delaying execution'); // last job has started less than an hour ago => we consider it can still be running $this->jobList->add(ImportDriveJob::class, ['user_id' => $userId]); return; } } - $this->config->setUserValue($userId, Application::APP_ID, 'drive_import_running', '1'); - $this->config->setUserValue($userId, Application::APP_ID, 'drive_import_job_last_start', strval($nowTs)); + $this->userConfig->setValueString($userId, Application::APP_ID, 'drive_import_running', '1', lazy: true); + $this->userConfig->setValueInt($userId, Application::APP_ID, 'drive_import_job_last_start', $nowTs, lazy: true); // import batch of files - $targetPath = $this->config->getUserValue($userId, Application::APP_ID, 'drive_output_dir', '/Google Drive'); + $targetPath = $this->userConfig->getValueString($userId, APPLICATION::APP_ID, 'drive_output_dir', '/Google Drive', lazy:true); $targetPath = $targetPath ?: '/Google Drive'; - $targetSharedPath = $this->config->getUserValue($userId, Application::APP_ID, 'drive_shared_with_me_output_dir', '/Google Drive/Shared with me'); + $targetSharedPath = $this->userConfig->getValueString($userId, APPLICATION::APP_ID, 'drive_shared_with_me_output_dir', '/Google Drive/Shared with me', lazy:true); $targetSharedPath = $targetSharedPath ?: '/Google Drive/Shared with me'; // check if target paths are suitable @@ -200,7 +198,7 @@ public function importDriveJob(string $userId): void { $targetSharedPath = $this->getNonSharedTargetPath($userId, $targetSharedPath); // get progress - $directoryProgressStr = $this->config->getUserValue($userId, Application::APP_ID, 'directory_progress', '[]'); + $directoryProgressStr = $this->userConfig->getValueString($userId, APPLICATION::APP_ID, 'directory_progress', '[]', lazy:true); $directoryProgress = ($directoryProgressStr === '' || $directoryProgressStr === '[]') ? [] : json_decode($directoryProgressStr, true); @@ -217,7 +215,7 @@ public function importDriveJob(string $userId): void { } if (isset($result['error']) || (isset($result['finished']) && $result['finished'])) { if (isset($result['finished']) && $result['finished']) { - $nbImported = (int)$this->config->getUserValue($userId, Application::APP_ID, 'nb_imported_files', '0'); + $nbImported = $this->userConfig->getValueInt($userId, APPLICATION::APP_ID, 'nb_imported_files', lazy:true); $this->googleApiService->sendNCNotification($userId, 'import_drive_finished', [ 'nbImported' => $nbImported, 'targetPath' => $targetPath, @@ -226,18 +224,18 @@ public function importDriveJob(string $userId): void { if (isset($result['error'])) { $this->logger->error('Google Drive import error: ' . $result['error'], ['app' => Application::APP_ID]); } - $this->config->setUserValue($userId, Application::APP_ID, 'importing_drive', '0'); - $this->config->setUserValue($userId, Application::APP_ID, 'nb_imported_files', '0'); - $this->config->setUserValue($userId, Application::APP_ID, 'drive_imported_size', '0'); - $this->config->setUserValue($userId, Application::APP_ID, 'last_drive_import_timestamp', '0'); - $this->config->deleteUserValue($userId, Application::APP_ID, 'directory_progress'); + $this->userConfig->setValueString($userId, Application::APP_ID, 'importing_drive', '0', lazy: true); + $this->userConfig->setValueInt($userId, Application::APP_ID, 'nb_imported_files', 0, lazy: true); + $this->userConfig->setValueInt($userId, Application::APP_ID, 'drive_imported_size', 0, lazy: true); + $this->userConfig->setValueInt($userId, Application::APP_ID, 'last_drive_import_timestamp', 0, lazy: true); + $this->userConfig->deleteUserConfig($userId, Application::APP_ID, 'directory_progress'); } else { - $this->config->setUserValue($userId, Application::APP_ID, 'directory_progress', json_encode($directoryProgress)); + $this->userConfig->setValueString($userId, Application::APP_ID, 'directory_progress', json_encode($directoryProgress), lazy: true); $ts = (new DateTime())->getTimestamp(); - $this->config->setUserValue($userId, Application::APP_ID, 'last_drive_import_timestamp', $ts); + $this->userConfig->setValueInt($userId, Application::APP_ID, 'last_drive_import_timestamp', $ts, lazy: true); $this->jobList->add(ImportDriveJob::class, ['user_id' => $userId]); } - $this->config->setUserValue($userId, Application::APP_ID, 'drive_import_running', '0'); + $this->userConfig->setValueString($userId, Application::APP_ID, 'drive_import_running', '0', lazy: true); } /** @@ -255,7 +253,7 @@ public function importFiles( string $userId, string $targetPath, string $targetSharedPath, ?int $maxDownloadSize = null, array &$directoryProgress = [], ): array { - $considerSharedFiles = $this->config->getUserValue($userId, Application::APP_ID, 'consider_shared_files', '0') === '1'; + $considerSharedFiles = $this->userConfig->getValueString($userId, APPLICATION::APP_ID, 'consider_shared_files', '0', lazy:true) === '1'; // create root folder(s) $userFolder = $this->root->getUserFolder($userId); @@ -375,7 +373,7 @@ public function importFiles( */ public function hasBeenCancelled(string $userId): bool { $this->userConfig->clearCache($userId); - return $this->config->getUserValue($userId, Application::APP_ID, 'importing_drive', '0') === '0'; + return $this->userConfig->getValueString($userId, APPLICATION::APP_ID, 'importing_drive', '0', lazy:true) === '0'; } /** @@ -646,7 +644,7 @@ private function getFileName(array $fileItem, string $userId, bool $hasNameConfl * @return string User's preferred document format */ private function getUserDocumentFormat(string $userId): string { - $documentFormat = $this->config->getUserValue($userId, Application::APP_ID, 'document_format', 'openxml'); + $documentFormat = $this->userConfig->getValueString($userId, APPLICATION::APP_ID, 'document_format', 'openxml', lazy:true); if (!in_array($documentFormat, ['openxml', 'opendoc'])) { $documentFormat = 'openxml'; } @@ -846,8 +844,8 @@ private function recursivelyCheckParentOwnership(string $rootId, array $director */ private function retrieveFiles(string $userId, string $dirId, string $query, bool $considerSharedFiles, Folder $rootImportFolder, ?Folder $rootSharedWithMeImportFolder, array $directoriesById, array $sharedDirectoriesById, ?int $maxDownloadSize, string $targetPath, bool $allowParents = true): ?array { $lastCancelCheck = time(); - $alreadyImported = (int)$this->config->getUserValue($userId, Application::APP_ID, 'nb_imported_files', '0'); - $alreadyImportedSize = (int)$this->config->getUserValue($userId, Application::APP_ID, 'drive_imported_size', '0'); + $alreadyImported = $this->userConfig->getValueInt($userId, APPLICATION::APP_ID, 'nb_imported_files', lazy:true); + $alreadyImportedSize = $this->userConfig->getValueInt($userId, APPLICATION::APP_ID, 'drive_imported_size', lazy:true); $conflictingIds = $this->getFilesWithNameConflict($userId, $query, $considerSharedFiles); $params = [ @@ -921,9 +919,9 @@ private function retrieveFiles(string $userId, string $dirId, string $query, boo if (!is_null($size)) { $alreadyImported++; - $this->config->setUserValue($userId, Application::APP_ID, 'nb_imported_files', strval($alreadyImported)); + $this->userConfig->setValueInt($userId, Application::APP_ID, 'nb_imported_files', $alreadyImported, lazy: true); $alreadyImportedSize += $size; - $this->config->setUserValue($userId, Application::APP_ID, 'drive_imported_size', strval($alreadyImportedSize)); + $this->userConfig->setValueInt($userId, Application::APP_ID, 'drive_imported_size', $alreadyImportedSize, lazy: true); if ($maxDownloadSize !== null && $alreadyImportedSize > $maxDownloadSize) { return [ 'targetPath' => $targetPath, diff --git a/lib/Service/SecretService.php b/lib/Service/SecretService.php index bcb52f35..1289fb71 100644 --- a/lib/Service/SecretService.php +++ b/lib/Service/SecretService.php @@ -3,15 +3,13 @@ namespace OCA\Google\Service; use OCA\Google\AppInfo\Application; -use OCP\IConfig; -use OCP\IUserManager; +use OCP\Config\IUserConfig; use OCP\PreConditionNotMetException; use OCP\Security\ICrypto; class SecretService { public function __construct( - private IConfig $config, - private IUserManager $userManager, + private IUserConfig $userConfig, private ICrypto $crypto, ) { } @@ -25,11 +23,11 @@ public function __construct( */ public function setEncryptedUserValue(string $userId, string $key, string $value): void { if ($value === '') { - $this->config->setUserValue($userId, Application::APP_ID, $key, ''); + $this->userConfig->setValueString($userId, Application::APP_ID, $key, '', lazy: true); return; } $encryptedValue = $this->crypto->encrypt($value); - $this->config->setUserValue($userId, Application::APP_ID, $key, $encryptedValue); + $this->userConfig->setValueString($userId, Application::APP_ID, $key, $encryptedValue, lazy: true); } /** @@ -39,34 +37,7 @@ public function setEncryptedUserValue(string $userId, string $key, string $value * @throws \Exception */ public function getEncryptedUserValue(string $userId, string $key): string { - $storedValue = $this->config->getUserValue($userId, Application::APP_ID, $key); - if ($storedValue === '') { - return ''; - } - return $this->crypto->decrypt($storedValue); - } - - /** - * @param string $key - * @param string $value - * @return void - */ - public function setEncryptedAppValue(string $key, string $value): void { - if ($value === '') { - $this->config->setAppValue(Application::APP_ID, $key, ''); - return; - } - $encryptedValue = $this->crypto->encrypt($value); - $this->config->setAppValue(Application::APP_ID, $key, $encryptedValue); - } - - /** - * @param string $key - * @return string - * @throws \Exception - */ - public function getEncryptedAppValue(string $key): string { - $storedValue = $this->config->getAppValue(Application::APP_ID, $key); + $storedValue = $this->userConfig->getValueString($userId, Application::APP_ID, $key, lazy: true); if ($storedValue === '') { return ''; } diff --git a/lib/Service/Utils/FileUtils.php b/lib/Service/Utils/FileUtils.php index f5cea353..231cc2fa 100644 --- a/lib/Service/Utils/FileUtils.php +++ b/lib/Service/Utils/FileUtils.php @@ -2,11 +2,7 @@ namespace OCA\Google\Service\Utils; -use OCP\Files\EmptyFileNameException; -use OCP\Files\FileNameTooLongException; -use OCP\Files\InvalidCharacterInPathException; -use OCP\Files\InvalidDirectoryException; -use OCP\Files\ReservedWordException; +use OCP\Files\IFilenameValidator; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; use Psr\Log\LoggerInterface; @@ -14,8 +10,7 @@ final class FileUtils { public function __construct( - private \OCP\Files\IFilenameValidator $validator, - private \OCP\IConfig $config, + private IFilenameValidator $validator, private LoggerInterface $logger, ) { } @@ -25,111 +20,19 @@ public function __construct( * * @param string $filename The original filename to sanitize. * @param string $id A unique ID to append if necessary to ensure uniqueness. - * @param int $recursionDepth The current recursion depth (used to prevent infinite loops). - * @param string|null $originalFilename The original filename for logging. * @return string The sanitized and validated filename. */ public function sanitizeFilename( string $filename, string $id, - int $recursionDepth = 0, - ?string $originalFilename = null, ): string { - if ($recursionDepth > 15) { - $filename = 'Untitled_' . $id; - $this->logger->warning('Maximum recursion depth reached while sanitizing filename: ' . ($originalFilename ?? $filename) . ' renaming to ' . $filename); - return $filename; - } - - if ($originalFilename === null) { - $originalFilename = $filename; - } - - // Use Nextcloud 32+ validator if available - if (version_compare($this->config->getSystemValueString('version', '0.0.0'), '32.0.0', '>=')) { - $this->logger->debug('Using Nextcloud 32+ filename validator for sanitization.'); - try { - return $this->validator->sanitizeFilename($filename); - } catch (\InvalidArgumentException|NotFoundExceptionInterface|ContainerExceptionInterface $exception) { - $this->logger->error('Unable to sanitize filename: ' . $filename, ['exception' => $exception]); - return 'Untitled_' . $id; - } - } else { - $this->logger->debug('Using legacy filename sanitization method.'); - } - - // Trim whitespace and trailing dots - $filename = rtrim(trim($filename), '.'); - - // Append ID if needed - if ($originalFilename !== $filename && strpos($filename, $id) === false) { - $filename = self::appendIdBeforeExtension($filename, $id); - } - - // Enforce max length - $maxLength = 254; - if (mb_strlen($filename) > $maxLength) { - $filename = self::truncateAndAppendId($filename, $id, $maxLength); - } + // Use Nextcloud 32+ validator try { - $this->validator->validateFilename($filename); - if ($recursionDepth > 0) { - $this->logger->info('Filename sanitized successfully: "' . $filename . '" (original: "' . $originalFilename . '")'); - } - return $filename; - } catch (\Throwable $exception) { - $this->logger->warning('Exception during filename validation: ' . $filename, ['exception' => $exception]); - $filename = self::handleFilenameException($filename, $id, $exception, $this->logger); - if (strpos($filename, $id) === false) { - $filename = self::appendIdBeforeExtension($filename, $id); - } - return $this->sanitizeFilename($filename, $id, $recursionDepth + 1, $originalFilename); - } - } - - private static function appendIdBeforeExtension(string $filename, string $id): string { - $pathInfo = pathinfo($filename); - if (isset($pathInfo['extension'])) { - return $pathInfo['filename'] . '_' . $id . '.' . $pathInfo['extension']; - } - return $filename . '_' . $id; - } - - private static function truncateAndAppendId(string $filename, string $id, int $maxLength): string { - $pathInfo = pathinfo($filename); - $baseLength = $maxLength - mb_strlen($id) - 2; - if (isset($pathInfo['extension'])) { - $baseLength -= mb_strlen($pathInfo['extension']); - return mb_substr($pathInfo['filename'], 0, $baseLength) . '_' . $id . '.' . $pathInfo['extension']; - } - return mb_substr($filename, 0, $baseLength) . '_' . $id; - } - - private static function handleFilenameException(string $filename, string $id, \Throwable $exception, LoggerInterface $logger): string { - if ($exception instanceof FileNameTooLongException) { - return mb_substr($filename, 0, 254 - mb_strlen($id) - 2); - } - if ($exception instanceof EmptyFileNameException) { - return 'Untitled'; - } - if ($exception instanceof InvalidCharacterInPathException) { - if (preg_match('/"(.*?)"/', $exception->getMessage(), $matches)) { - $invalidChars = array_merge(str_split($matches[1]), ['"']); - return str_replace($invalidChars, '-', $filename); - } - } - if ($exception instanceof InvalidDirectoryException) { - $logger->error('Invalid directory detected in filename: ' . $exception->getMessage()); - return 'Untitled'; - } - if ($exception instanceof ReservedWordException) { - if (preg_match('/"(.*?)"/', $exception->getMessage(), $matches)) { - $reservedWord = $matches[1]; - return str_ireplace($reservedWord, '-' . $reservedWord . '-', $filename); - } + return $this->validator->sanitizeFilename($filename); + } catch (\InvalidArgumentException|NotFoundExceptionInterface|ContainerExceptionInterface $exception) { + $this->logger->error('Unable to sanitize filename: ' . $filename, ['exception' => $exception]); + return 'Untitled_' . $id; } - $logger->error('Unknown exception encountered during filename sanitization: ' . $filename); - return 'Untitled'; } } diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php index 176796af..c4e2c5ca 100644 --- a/lib/Settings/Admin.php +++ b/lib/Settings/Admin.php @@ -3,18 +3,16 @@ namespace OCA\Google\Settings; use OCA\Google\AppInfo\Application; -use OCA\Google\Service\SecretService; use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Services\IAppConfig; use OCP\AppFramework\Services\IInitialState; -use OCP\IConfig; use OCP\Settings\ISettings; class Admin implements ISettings { public function __construct( - private IConfig $config, + private IAppConfig $appConfig, private IInitialState $initialStateService, - private SecretService $secretService, ) { } @@ -22,9 +20,9 @@ public function __construct( * @return TemplateResponse */ public function getForm(): TemplateResponse { - $clientID = $this->secretService->getEncryptedAppValue('client_id'); - $clientSecret = $this->secretService->getEncryptedAppValue('client_secret'); - $usePopup = $this->config->getAppValue(Application::APP_ID, 'use_popup', '0'); + $clientID = $this->appConfig->getAppValueString('client_id', lazy: true); + $clientSecret = $this->appConfig->getAppValueString('client_secret', lazy: true); + $usePopup = $this->appConfig->getAppValueString('use_popup', '0', lazy: true); $adminConfig = [ 'client_id' => $clientID, diff --git a/lib/Settings/Personal.php b/lib/Settings/Personal.php index dfbf5a0a..50c85112 100644 --- a/lib/Settings/Personal.php +++ b/lib/Settings/Personal.php @@ -7,12 +7,13 @@ use OCA\Google\Service\GoogleAPIService; use OCA\Google\Service\SecretService; use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Services\IAppConfig; use OCP\AppFramework\Services\IInitialState; +use OCP\Config\IUserConfig; use OCP\Files\Folder; use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; -use OCP\IConfig; use OCP\IUserManager; use OCP\Settings\ISettings; use Throwable; @@ -20,7 +21,8 @@ class Personal implements ISettings { public function __construct( - private IConfig $config, + private IAppConfig $appConfig, + private IUserConfig $userConfig, private IRootFolder $root, private IUserManager $userManager, private IInitialState $initialStateService, @@ -40,24 +42,24 @@ public function getForm(): TemplateResponse { if ($this->userId === null) { return new TemplateResponse(Application::APP_ID, 'personalSettings'); } - $userName = $this->config->getUserValue($this->userId, Application::APP_ID, 'user_name'); - $driveOutputDir = $this->config->getUserValue($this->userId, Application::APP_ID, 'drive_output_dir', '/Google Drive'); + $userName = $this->userConfig->getValueString($this->userId, Application::APP_ID, 'user_name', lazy: true); + $driveOutputDir = $this->userConfig->getValueString($this->userId, Application::APP_ID, 'drive_output_dir', '/Google Drive', lazy: true); $driveOutputDir = $driveOutputDir ?: '/Google Drive'; - $driveSharedWithMeOutputDir = $this->config->getUserValue($this->userId, Application::APP_ID, 'drive_shared_with_me_output_dir', '/Google Drive/Shared with me'); + $driveSharedWithMeOutputDir = $this->userConfig->getValueString($this->userId, Application::APP_ID, 'drive_shared_with_me_output_dir', '/Google Drive/Shared with me', lazy: true); $driveSharedWithMeOutputDir = $driveSharedWithMeOutputDir ?: '/Google Drive/Shared with me'; - $considerAllEvents = $this->config->getUserValue($this->userId, Application::APP_ID, 'consider_all_events', '1') === '1'; - $considerSharedFiles = $this->config->getUserValue($this->userId, Application::APP_ID, 'consider_shared_files', '0') === '1'; - $considerSharedAlbums = $this->config->getUserValue($this->userId, Application::APP_ID, 'consider_shared_albums', '0') === '1'; - $considerOtherContacts = $this->config->getUserValue($this->userId, Application::APP_ID, 'consider_other_contacts', '0') === '1'; - $documentFormat = $this->config->getUserValue($this->userId, Application::APP_ID, 'document_format', 'openxml'); + $considerAllEvents = $this->userConfig->getValueString($this->userId, Application::APP_ID, 'consider_all_events', '1', lazy: true) === '1'; + $considerSharedFiles = $this->userConfig->getValueString($this->userId, Application::APP_ID, 'consider_shared_files', '0', lazy: true) === '1'; + $considerSharedAlbums = $this->userConfig->getValueString($this->userId, Application::APP_ID, 'consider_shared_albums', '0', lazy: true) === '1'; + $considerOtherContacts = $this->userConfig->getValueString($this->userId, Application::APP_ID, 'consider_other_contacts', '0', lazy: true) === '1'; + $documentFormat = $this->userConfig->getValueString($this->userId, Application::APP_ID, 'document_format', 'openxml', lazy: true); if (!in_array($documentFormat, ['openxml', 'opendoc'])) { $documentFormat = 'openxml'; } // for OAuth - $clientID = $this->secretService->getEncryptedAppValue('client_id'); - $clientSecret = $this->secretService->getEncryptedAppValue('client_secret') !== ''; - $usePopup = $this->config->getAppValue(Application::APP_ID, 'use_popup', '0'); + $clientID = $this->appConfig->getAppValueString('client_id', lazy: true); + $clientSecret = $this->appConfig->getAppValueString('client_secret', lazy: true) !== ''; + $usePopup = $this->appConfig->getAppValueString('use_popup', '0', lazy: true); // get free space $userFolder = $this->root->getUserFolder($this->userId); @@ -71,7 +73,7 @@ public function getForm(): TemplateResponse { } // Get scopes of user - $userScopesString = $this->config->getUserValue($this->userId, Application::APP_ID, 'user_scopes', '{}'); + $userScopesString = $this->userConfig->getValueString($this->userId, Application::APP_ID, 'user_scopes', '{}', lazy: true); /** @var bool|null|array $userScopes */ $userScopes = json_decode($userScopesString, true); if (!is_array($userScopes)) { diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 2b7f586d..a96d5b28 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -16,6 +16,11 @@ + + + + + @@ -24,9 +29,6 @@ - - - getStatusCode()]]> getStatusCode()]]> @@ -149,19 +151,6 @@ - - - - - - - - - - - - -