diff options
Diffstat (limited to 'apps/updatenotification/lib/Controller')
3 files changed, 331 insertions, 0 deletions
diff --git a/apps/updatenotification/lib/Controller/APIController.php b/apps/updatenotification/lib/Controller/APIController.php new file mode 100644 index 00000000000..4360d814dd2 --- /dev/null +++ b/apps/updatenotification/lib/Controller/APIController.php @@ -0,0 +1,197 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\UpdateNotification\Controller; + +use OC\App\AppStore\Fetcher\AppFetcher; +use OCA\UpdateNotification\Manager; +use OCA\UpdateNotification\ResponseDefinitions; +use OCP\App\AppPathNotFoundException; +use OCP\App\IAppManager; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\OCSController; +use OCP\IConfig; +use OCP\IRequest; +use OCP\IUserSession; +use OCP\L10N\IFactory; + +/** + * @psalm-import-type UpdateNotificationApp from ResponseDefinitions + */ +class APIController extends OCSController { + + protected ?string $language = null; + + /** + * List of apps that were in the appstore but are now shipped and don't have + * a compatible update available. + * + * @var array<string, int> + */ + protected array $appsShippedInFutureVersion = [ + 'bruteforcesettings' => 25, + 'suspicious_login' => 25, + 'twofactor_totp' => 25, + 'files_downloadlimit' => 29, + 'twofactor_nextcloud_notification' => 30, + 'app_api' => 30, + ]; + + public function __construct( + string $appName, + IRequest $request, + protected IConfig $config, + protected IAppManager $appManager, + protected AppFetcher $appFetcher, + protected IFactory $l10nFactory, + protected IUserSession $userSession, + protected Manager $manager, + ) { + parent::__construct($appName, $request); + } + + /** + * List available updates for apps + * + * @param string $newVersion Server version to check updates for + * + * @return DataResponse<Http::STATUS_OK, array{missing: list<UpdateNotificationApp>, available: list<UpdateNotificationApp>}, array{}>|DataResponse<Http::STATUS_NOT_FOUND, array{appstore_disabled: bool, already_on_latest?: bool}, array{}> + * + * 200: Apps returned + * 404: New versions not found + */ + public function getAppList(string $newVersion): DataResponse { + if (!$this->config->getSystemValue('appstoreenabled', true)) { + return new DataResponse([ + 'appstore_disabled' => true, + ], Http::STATUS_NOT_FOUND); + } + + // Get list of installed custom apps + $installedApps = $this->appManager->getEnabledApps(); + $installedApps = array_filter($installedApps, function ($app) { + try { + $this->appManager->getAppPath($app); + } catch (AppPathNotFoundException $e) { + return false; + } + return !$this->appManager->isShipped($app) && !isset($this->appsShippedInFutureVersion[$app]); + }); + + if (empty($installedApps)) { + return new DataResponse([ + 'missing' => [], + 'available' => [], + ]); + } + + $this->appFetcher->setVersion($newVersion, 'future-apps.json', false); + + // Apps available on the app store for that version + $availableApps = array_map(static function (array $app): string { + return $app['id']; + }, $this->appFetcher->get()); + + if (empty($availableApps)) { + return new DataResponse([ + 'appstore_disabled' => false, + 'already_on_latest' => false, + ], Http::STATUS_NOT_FOUND); + } + + // Ignore apps that are deployed from git + $installedApps = array_filter($installedApps, function (string $appId) { + try { + return !file_exists($this->appManager->getAppPath($appId) . '/.git'); + } catch (AppPathNotFoundException $e) { + return true; + } + }); + + $missing = array_diff($installedApps, $availableApps); + $missing = array_map([$this, 'getAppDetails'], $missing); + sort($missing); + + $available = array_intersect($installedApps, $availableApps); + $available = array_map([$this, 'getAppDetails'], $available); + sort($available); + + return new DataResponse([ + 'missing' => $missing, + 'available' => $available, + ]); + } + + /** + * Get translated app name + * + * @param string $appId + * @return UpdateNotificationApp + */ + protected function getAppDetails(string $appId): array { + $app = $this->appManager->getAppInfo($appId, false, $this->language); + $name = $app['name'] ?? $appId; + return [ + 'appId' => $appId, + 'appName' => $name, + ]; + } + + protected function getLanguage(): string { + if ($this->language === null) { + $this->language = $this->l10nFactory->getUserLanguage($this->userSession->getUser()); + } + return $this->language; + } + + /** + * Get changelog entry for an app + * + * @param string $appId App to search changelog entry for + * @param string|null $version The version to search the changelog entry for (defaults to the latest installed) + * + * @return DataResponse<Http::STATUS_OK, array{appName: string, content: string, version: string}, array{}>|DataResponse<Http::STATUS_NOT_FOUND, array{}, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array{}, array{}> + * + * 200: Changelog entry returned + * 400: The `version` parameter is not a valid version format + * 404: No changelog found + */ + public function getAppChangelogEntry(string $appId, ?string $version = null): DataResponse { + $version = $version ?? $this->appManager->getAppVersion($appId); + // handle pre-release versions + $matches = []; + $result = preg_match('/^(\d+\.\d+(\.\d+)?)/', $version, $matches); + if ($result === false || $result === 0) { + return new DataResponse([], Http::STATUS_BAD_REQUEST); + } + $shortVersion = $matches[0]; + + $changes = $this->manager->getChangelog($appId, $shortVersion); + + if ($changes === null) { + return new DataResponse([], Http::STATUS_NOT_FOUND); + } + + // Remove version headline + /** @var string[] */ + $changes = explode("\n", $changes, 2); + $changes = trim(end($changes)); + + // Get app info for localized app name + $info = $this->appManager->getAppInfo($appId) ?? []; + /** @var string */ + $appName = $info['name'] ?? $appId; + + return new DataResponse([ + 'appName' => $appName, + 'content' => $changes, + 'version' => $version, + ]); + } +} diff --git a/apps/updatenotification/lib/Controller/AdminController.php b/apps/updatenotification/lib/Controller/AdminController.php new file mode 100644 index 00000000000..26745948890 --- /dev/null +++ b/apps/updatenotification/lib/Controller/AdminController.php @@ -0,0 +1,72 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OCA\UpdateNotification\Controller; + +use OCA\UpdateNotification\BackgroundJob\ResetToken; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\BackgroundJob\IJobList; +use OCP\IAppConfig; +use OCP\IConfig; +use OCP\IL10N; +use OCP\IRequest; +use OCP\Security\ISecureRandom; +use OCP\Util; + +class AdminController extends Controller { + + public function __construct( + string $appName, + IRequest $request, + private IJobList $jobList, + private ISecureRandom $secureRandom, + private IConfig $config, + private IAppConfig $appConfig, + private ITimeFactory $timeFactory, + private IL10N $l10n, + ) { + parent::__construct($appName, $request); + } + + private function isUpdaterEnabled(): bool { + return !$this->config->getSystemValueBool('upgrade.disable-web'); + } + + /** + * @param string $channel + * @return DataResponse + */ + public function setChannel(string $channel): DataResponse { + Util::setChannel($channel); + $this->appConfig->setValueInt('core', 'lastupdatedat', 0); + return new DataResponse(['status' => 'success', 'data' => ['message' => $this->l10n->t('Channel updated')]]); + } + + /** + * @return DataResponse + */ + public function createCredentials(): DataResponse { + if (!$this->isUpdaterEnabled()) { + return new DataResponse(['status' => 'error', 'message' => $this->l10n->t('Web updater is disabled')], Http::STATUS_FORBIDDEN); + } + + // Create a new job and store the creation date + $this->jobList->add(ResetToken::class); + $this->appConfig->setValueInt('core', 'updater.secret.created', $this->timeFactory->getTime()); + + // Create a new token + $newToken = $this->secureRandom->generate(64); + $this->config->setSystemValue('updater.secret', password_hash($newToken, PASSWORD_DEFAULT)); + + return new DataResponse($newToken); + } +} diff --git a/apps/updatenotification/lib/Controller/ChangelogController.php b/apps/updatenotification/lib/Controller/ChangelogController.php new file mode 100644 index 00000000000..a274ed3d2b2 --- /dev/null +++ b/apps/updatenotification/lib/Controller/ChangelogController.php @@ -0,0 +1,62 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\UpdateNotification\Controller; + +use OCA\UpdateNotification\Manager; +use OCP\App\IAppManager; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\Attribute\NoCSRFRequired; +use OCP\AppFramework\Http\Attribute\OpenAPI; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Services\IInitialState; +use OCP\IRequest; +use OCP\Util; + +#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)] +class ChangelogController extends Controller { + + public function __construct( + string $appName, + IRequest $request, + private Manager $manager, + private IAppManager $appManager, + private IInitialState $initialState, + ) { + parent::__construct($appName, $request); + } + + /** + * This page is only used for clients not support showing the app changelog feature in-app and thus need to show it on a dedicated page. + * @param string $app App to show the changelog for + * @param string|null $version Version entry to show (defaults to latest installed) + */ + #[NoAdminRequired] + #[NoCSRFRequired] + public function showChangelog(string $app, ?string $version = null): TemplateResponse { + $version = $version ?? $this->appManager->getAppVersion($app); + $appInfo = $this->appManager->getAppInfo($app) ?? []; + $appName = $appInfo['name'] ?? $app; + + $changes = $this->manager->getChangelog($app, $version) ?? ''; + // Remove version headline + /** @var string[] */ + $changes = explode("\n", $changes, 2); + $changes = trim(end($changes)); + + $this->initialState->provideInitialState('changelog', [ + 'appName' => $appName, + 'appVersion' => $version, + 'text' => $changes, + ]); + + Util::addScript($this->appName, 'view-changelog-page'); + return new TemplateResponse($this->appName, 'empty'); + } +} |