aboutsummaryrefslogtreecommitdiffstats
path: root/apps/updatenotification/lib/Controller
diff options
context:
space:
mode:
Diffstat (limited to 'apps/updatenotification/lib/Controller')
-rw-r--r--apps/updatenotification/lib/Controller/APIController.php197
-rw-r--r--apps/updatenotification/lib/Controller/AdminController.php72
-rw-r--r--apps/updatenotification/lib/Controller/ChangelogController.php62
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');
+ }
+}