aboutsummaryrefslogtreecommitdiffstats
path: root/core/Controller
diff options
context:
space:
mode:
Diffstat (limited to 'core/Controller')
-rw-r--r--core/Controller/AppPasswordController.php56
-rw-r--r--core/Controller/AutoCompleteController.php40
-rw-r--r--core/Controller/AvatarController.php113
-rw-r--r--core/Controller/CSRFTokenController.php40
-rw-r--r--core/Controller/ClientFlowLoginController.php117
-rw-r--r--core/Controller/ClientFlowLoginV2Controller.php110
-rw-r--r--core/Controller/CollaborationResourcesController.php68
-rw-r--r--core/Controller/ContactsMenuController.php31
-rw-r--r--core/Controller/CssController.php37
-rw-r--r--core/Controller/ErrorController.php39
-rw-r--r--core/Controller/GuestAvatarController.php55
-rw-r--r--core/Controller/HoverCardController.php35
-rw-r--r--core/Controller/JsController.php37
-rw-r--r--core/Controller/LoginController.php115
-rw-r--r--core/Controller/LostController.php85
-rw-r--r--core/Controller/NavigationController.php53
-rw-r--r--core/Controller/OCJSController.php48
-rw-r--r--core/Controller/OCMController.php46
-rw-r--r--core/Controller/OCSController.php61
-rw-r--r--core/Controller/PreviewController.php103
-rw-r--r--core/Controller/ProfileApiController.php107
-rw-r--r--core/Controller/ProfilePageController.php126
-rw-r--r--core/Controller/RecommendedAppsController.php25
-rw-r--r--core/Controller/ReferenceApiController.php130
-rw-r--r--core/Controller/ReferenceController.php30
-rw-r--r--core/Controller/SearchController.php65
-rw-r--r--core/Controller/SetupController.php71
-rw-r--r--core/Controller/TaskProcessingApiController.php657
-rw-r--r--core/Controller/TeamsApiController.php37
-rw-r--r--core/Controller/TextProcessingApiController.php50
-rw-r--r--core/Controller/TextToImageApiController.php38
-rw-r--r--core/Controller/TranslationApiController.php41
-rw-r--r--core/Controller/TwoFactorApiController.php99
-rw-r--r--core/Controller/TwoFactorChallengeController.php65
-rw-r--r--core/Controller/UnifiedSearchController.php47
-rw-r--r--core/Controller/UnsupportedBrowserController.php33
-rw-r--r--core/Controller/UserController.php29
-rw-r--r--core/Controller/WalledGardenController.php32
-rw-r--r--core/Controller/WebAuthnController.php34
-rw-r--r--core/Controller/WellKnownController.php31
-rw-r--r--core/Controller/WhatsNewController.php46
-rw-r--r--core/Controller/WipeController.php49
42 files changed, 1634 insertions, 1497 deletions
diff --git a/core/Controller/AppPasswordController.php b/core/Controller/AppPasswordController.php
index 11aca8ef329..e5edc165bf5 100644
--- a/core/Controller/AppPasswordController.php
+++ b/core/Controller/AppPasswordController.php
@@ -3,28 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2018, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
@@ -34,9 +14,13 @@ use OC\Authentication\Token\IToken;
use OC\User\Session;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
+use OCP\AppFramework\Http\Attribute\BruteForceProtection;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
use OCP\AppFramework\Http\Attribute\UseSession;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSForbiddenException;
+use OCP\AppFramework\OCSController;
use OCP\Authentication\Exceptions\CredentialsUnavailableException;
use OCP\Authentication\Exceptions\InvalidTokenException;
use OCP\Authentication\Exceptions\PasswordUnavailableException;
@@ -48,7 +32,7 @@ use OCP\IUserManager;
use OCP\Security\Bruteforce\IThrottler;
use OCP\Security\ISecureRandom;
-class AppPasswordController extends \OCP\AppFramework\OCSController {
+class AppPasswordController extends OCSController {
public function __construct(
string $appName,
IRequest $request,
@@ -65,9 +49,6 @@ class AppPasswordController extends \OCP\AppFramework\OCSController {
}
/**
- * @NoAdminRequired
- * @PasswordConfirmationRequired
- *
* Create app password
*
* @return DataResponse<Http::STATUS_OK, array{apppassword: string}, array{}>
@@ -75,6 +56,8 @@ class AppPasswordController extends \OCP\AppFramework\OCSController {
*
* 200: App password returned
*/
+ #[NoAdminRequired]
+ #[PasswordConfirmationRequired]
#[ApiRoute(verb: 'GET', url: '/getapppassword', root: '/core')]
public function getAppPassword(): DataResponse {
// We do not allow the creation of new tokens if this is an app password
@@ -94,9 +77,9 @@ class AppPasswordController extends \OCP\AppFramework\OCSController {
$password = null;
}
- $userAgent = $this->request->getHeader('USER_AGENT');
+ $userAgent = $this->request->getHeader('user-agent');
- $token = $this->random->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
+ $token = $this->random->generate(72, ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS);
$generatedToken = $this->tokenProvider->generateToken(
$token,
@@ -118,15 +101,14 @@ class AppPasswordController extends \OCP\AppFramework\OCSController {
}
/**
- * @NoAdminRequired
- *
* Delete app password
*
- * @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
+ * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
* @throws OCSForbiddenException Deleting app password is not allowed
*
* 200: App password deleted successfully
*/
+ #[NoAdminRequired]
#[ApiRoute(verb: 'DELETE', url: '/apppassword', root: '/core')]
public function deleteAppPassword(): DataResponse {
if (!$this->session->exists('app_password')) {
@@ -146,8 +128,6 @@ class AppPasswordController extends \OCP\AppFramework\OCSController {
}
/**
- * @NoAdminRequired
- *
* Rotate app password
*
* @return DataResponse<Http::STATUS_OK, array{apppassword: string}, array{}>
@@ -155,6 +135,7 @@ class AppPasswordController extends \OCP\AppFramework\OCSController {
*
* 200: App password returned
*/
+ #[NoAdminRequired]
#[ApiRoute(verb: 'POST', url: '/apppassword/rotate', root: '/core')]
public function rotateAppPassword(): DataResponse {
if (!$this->session->exists('app_password')) {
@@ -169,7 +150,7 @@ class AppPasswordController extends \OCP\AppFramework\OCSController {
throw new OCSForbiddenException('could not rotate apptoken');
}
- $newToken = $this->random->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
+ $newToken = $this->random->generate(72, ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS);
$this->tokenProvider->rotate($token, $appPassword, $newToken);
return new DataResponse([
@@ -180,16 +161,15 @@ class AppPasswordController extends \OCP\AppFramework\OCSController {
/**
* Confirm the user password
*
- * @NoAdminRequired
- * @BruteForceProtection(action=sudo)
- *
* @param string $password The password of the user
*
- * @return DataResponse<Http::STATUS_OK, array{lastLogin: int}, array{}>|DataResponse<Http::STATUS_FORBIDDEN, array<empty>, array{}>
+ * @return DataResponse<Http::STATUS_OK, array{lastLogin: int}, array{}>|DataResponse<Http::STATUS_FORBIDDEN, list<empty>, array{}>
*
* 200: Password confirmation succeeded
* 403: Password confirmation failed
*/
+ #[NoAdminRequired]
+ #[BruteForceProtection(action: 'sudo')]
#[UseSession]
#[ApiRoute(verb: 'PUT', url: '/apppassword/confirm', root: '/core')]
public function confirmUserPassword(string $password): DataResponse {
diff --git a/core/Controller/AutoCompleteController.php b/core/Controller/AutoCompleteController.php
index 20170546ce5..692fe1b7297 100644
--- a/core/Controller/AutoCompleteController.php
+++ b/core/Controller/AutoCompleteController.php
@@ -3,36 +3,15 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
-use OCA\Core\ResponseDefinitions;
+use OC\Core\ResponseDefinitions;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\Collaboration\AutoComplete\AutoCompleteEvent;
@@ -58,21 +37,20 @@ class AutoCompleteController extends OCSController {
}
/**
- * @NoAdminRequired
- *
* Autocomplete a query
*
* @param string $search Text to search for
* @param string|null $itemType Type of the items to search for
* @param string|null $itemId ID of the items to search for
* @param string|null $sorter can be piped, top prio first, e.g.: "commenters|share-recipients"
- * @param int[] $shareTypes Types of shares to search for
+ * @param list<int> $shareTypes Types of shares to search for
* @param int $limit Maximum number of results to return
*
- * @return DataResponse<Http::STATUS_OK, CoreAutocompleteResult[], array{}>
+ * @return DataResponse<Http::STATUS_OK, list<CoreAutocompleteResult>, array{}>
*
* 200: Autocomplete results returned
*/
+ #[NoAdminRequired]
#[ApiRoute(verb: 'GET', url: '/autocomplete/get', root: '/core')]
public function get(string $search, ?string $itemType, ?string $itemId, ?string $sorter = null, array $shareTypes = [IShare::TYPE_USER], int $limit = 10): DataResponse {
// if enumeration/user listings are disabled, we'll receive an empty
@@ -122,7 +100,7 @@ class AutoCompleteController extends OCSController {
}
/**
- * @return CoreAutocompleteResult[]
+ * @return list<CoreAutocompleteResult>
*/
protected function prepareResultArray(array $results): array {
$output = [];
@@ -145,7 +123,7 @@ class AutoCompleteController extends OCSController {
$shareWithDisplayNameUnique = array_key_exists('shareWithDisplayNameUnique', $result) ? $result['shareWithDisplayNameUnique'] : null;
$output[] = [
- 'id' => (string) $result['value']['shareWith'],
+ 'id' => (string)$result['value']['shareWith'],
'label' => $label,
'icon' => $icon ?? '',
'source' => $type,
diff --git a/core/Controller/AvatarController.php b/core/Controller/AvatarController.php
index 03f59fd6439..b577b2fd460 100644
--- a/core/Controller/AvatarController.php
+++ b/core/Controller/AvatarController.php
@@ -1,48 +1,32 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Julien Veyssier <eneiluj@posteo.net>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Vincent Petry <vincent@nextcloud.com>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Controller;
use OC\AppFramework\Utility\TimeFactory;
+use OC\NotSquareException;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\DataDisplayResponse;
use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\AppFramework\Http\JSONResponse;
+use OCP\AppFramework\Http\Response;
use OCP\Files\File;
use OCP\Files\IRootFolder;
+use OCP\Files\NotPermittedException;
use OCP\IAvatarManager;
use OCP\ICache;
use OCP\IL10N;
+use OCP\Image;
use OCP\IRequest;
use OCP\IUserManager;
use Psr\Log\LoggerInterface;
@@ -64,27 +48,30 @@ class AvatarController extends Controller {
protected LoggerInterface $logger,
protected ?string $userId,
protected TimeFactory $timeFactory,
+ protected GuestAvatarController $guestAvatarController,
) {
parent::__construct($appName, $request);
}
/**
- * @NoAdminRequired
- * @NoCSRFRequired
* @NoSameSiteCookieRequired
- * @PublicPage
*
* Get the dark avatar
*
* @param string $userId ID of the user
- * @param int $size Size of the avatar
- * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string, X-NC-IsCustomAvatar: int}>|JSONResponse<Http::STATUS_NOT_FOUND, array<empty>, array{}>
+ * @param 64|512 $size Size of the avatar
+ * @param bool $guestFallback Fallback to guest avatar if not found
+ * @return FileDisplayResponse<Http::STATUS_OK|Http::STATUS_CREATED, array{Content-Type: string, X-NC-IsCustomAvatar: int}>|JSONResponse<Http::STATUS_NOT_FOUND, list<empty>, array{}>|Response<Http::STATUS_INTERNAL_SERVER_ERROR, array{}>
*
* 200: Avatar returned
+ * 201: Avatar returned
* 404: Avatar not found
*/
+ #[NoCSRFRequired]
+ #[PublicPage]
#[FrontpageRoute(verb: 'GET', url: '/avatar/{userId}/{size}/dark')]
- public function getAvatarDark(string $userId, int $size) {
+ #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
+ public function getAvatarDark(string $userId, int $size, bool $guestFallback = false) {
if ($size <= 64) {
if ($size !== 64) {
$this->logger->debug('Avatar requested in deprecated size ' . $size);
@@ -106,6 +93,9 @@ class AvatarController extends Controller {
['Content-Type' => $avatarFile->getMimeType(), 'X-NC-IsCustomAvatar' => (int)$avatar->isCustomAvatar()]
);
} catch (\Exception $e) {
+ if ($guestFallback) {
+ return $this->guestAvatarController->getAvatarDark($userId, $size);
+ }
return new JSONResponse([], Http::STATUS_NOT_FOUND);
}
@@ -116,22 +106,24 @@ class AvatarController extends Controller {
/**
- * @NoAdminRequired
- * @NoCSRFRequired
* @NoSameSiteCookieRequired
- * @PublicPage
*
* Get the avatar
*
* @param string $userId ID of the user
- * @param int $size Size of the avatar
- * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string, X-NC-IsCustomAvatar: int}>|JSONResponse<Http::STATUS_NOT_FOUND, array<empty>, array{}>
+ * @param 64|512 $size Size of the avatar
+ * @param bool $guestFallback Fallback to guest avatar if not found
+ * @return FileDisplayResponse<Http::STATUS_OK|Http::STATUS_CREATED, array{Content-Type: string, X-NC-IsCustomAvatar: int}>|JSONResponse<Http::STATUS_NOT_FOUND, list<empty>, array{}>|Response<Http::STATUS_INTERNAL_SERVER_ERROR, array{}>
*
* 200: Avatar returned
+ * 201: Avatar returned
* 404: Avatar not found
*/
+ #[NoCSRFRequired]
+ #[PublicPage]
#[FrontpageRoute(verb: 'GET', url: '/avatar/{userId}/{size}')]
- public function getAvatar(string $userId, int $size) {
+ #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
+ public function getAvatar(string $userId, int $size, bool $guestFallback = false) {
if ($size <= 64) {
if ($size !== 64) {
$this->logger->debug('Avatar requested in deprecated size ' . $size);
@@ -153,6 +145,9 @@ class AvatarController extends Controller {
['Content-Type' => $avatarFile->getMimeType(), 'X-NC-IsCustomAvatar' => (int)$avatar->isCustomAvatar()]
);
} catch (\Exception $e) {
+ if ($guestFallback) {
+ return $this->guestAvatarController->getAvatar($userId, $size);
+ }
return new JSONResponse([], Http::STATUS_NOT_FOUND);
}
@@ -161,9 +156,7 @@ class AvatarController extends Controller {
return $response;
}
- /**
- * @NoAdminRequired
- */
+ #[NoAdminRequired]
#[FrontpageRoute(verb: 'POST', url: '/avatar/')]
public function postAvatar(?string $path = null): JSONResponse {
$files = $this->request->getUploadedFile('files');
@@ -192,7 +185,7 @@ class AvatarController extends Controller {
try {
$content = $node->getContent();
- } catch (\OCP\Files\NotPermittedException $e) {
+ } catch (NotPermittedException $e) {
return new JSONResponse(
['data' => ['message' => $this->l10n->t('The selected file cannot be read.')]],
Http::STATUS_BAD_REQUEST
@@ -200,9 +193,8 @@ class AvatarController extends Controller {
}
} elseif (!is_null($files)) {
if (
- $files['error'][0] === 0 &&
- is_uploaded_file($files['tmp_name'][0]) &&
- !\OC\Files\Filesystem::isFileBlacklisted($files['tmp_name'][0])
+ $files['error'][0] === 0
+ && is_uploaded_file($files['tmp_name'][0])
) {
if ($files['size'][0] > 20 * 1024 * 1024) {
return new JSONResponse(
@@ -240,7 +232,7 @@ class AvatarController extends Controller {
}
try {
- $image = new \OCP\Image();
+ $image = new Image();
$image->loadFromData($content);
$image->readExif($content);
$image->fixOrientation();
@@ -284,9 +276,7 @@ class AvatarController extends Controller {
}
}
- /**
- * @NoAdminRequired
- */
+ #[NoAdminRequired]
#[FrontpageRoute(verb: 'DELETE', url: '/avatar/')]
public function deleteAvatar(): JSONResponse {
try {
@@ -300,21 +290,20 @@ class AvatarController extends Controller {
}
/**
- * @NoAdminRequired
- *
* @return JSONResponse|DataDisplayResponse
*/
+ #[NoAdminRequired]
#[FrontpageRoute(verb: 'GET', url: '/avatar/tmp')]
public function getTmpAvatar() {
$tmpAvatar = $this->cache->get('tmpAvatar');
if (is_null($tmpAvatar)) {
return new JSONResponse(['data' => [
- 'message' => $this->l10n->t("No temporary profile picture available, try again")
+ 'message' => $this->l10n->t('No temporary profile picture available, try again')
]],
Http::STATUS_NOT_FOUND);
}
- $image = new \OCP\Image();
+ $image = new Image();
$image->loadFromData($tmpAvatar);
$resp = new DataDisplayResponse(
@@ -328,30 +317,28 @@ class AvatarController extends Controller {
return $resp;
}
- /**
- * @NoAdminRequired
- */
+ #[NoAdminRequired]
#[FrontpageRoute(verb: 'POST', url: '/avatar/cropped')]
public function postCroppedAvatar(?array $crop = null): JSONResponse {
if (is_null($crop)) {
- return new JSONResponse(['data' => ['message' => $this->l10n->t("No crop data provided")]],
+ return new JSONResponse(['data' => ['message' => $this->l10n->t('No crop data provided')]],
Http::STATUS_BAD_REQUEST);
}
if (!isset($crop['x'], $crop['y'], $crop['w'], $crop['h'])) {
- return new JSONResponse(['data' => ['message' => $this->l10n->t("No valid crop data provided")]],
+ return new JSONResponse(['data' => ['message' => $this->l10n->t('No valid crop data provided')]],
Http::STATUS_BAD_REQUEST);
}
$tmpAvatar = $this->cache->get('tmpAvatar');
if (is_null($tmpAvatar)) {
return new JSONResponse(['data' => [
- 'message' => $this->l10n->t("No temporary profile picture available, try again")
+ 'message' => $this->l10n->t('No temporary profile picture available, try again')
]],
Http::STATUS_BAD_REQUEST);
}
- $image = new \OCP\Image();
+ $image = new Image();
$image->loadFromData($tmpAvatar);
$image->crop($crop['x'], $crop['y'], (int)round($crop['w']), (int)round($crop['h']));
try {
@@ -360,7 +347,7 @@ class AvatarController extends Controller {
// Clean up
$this->cache->remove('tmpAvatar');
return new JSONResponse(['status' => 'success']);
- } catch (\OC\NotSquareException $e) {
+ } catch (NotSquareException $e) {
return new JSONResponse(['data' => ['message' => $this->l10n->t('Crop is not square')]],
Http::STATUS_BAD_REQUEST);
} catch (\Exception $e) {
diff --git a/core/Controller/CSRFTokenController.php b/core/Controller/CSRFTokenController.php
index 13ea0011146..edf7c26e94c 100644
--- a/core/Controller/CSRFTokenController.php
+++ b/core/Controller/CSRFTokenController.php
@@ -3,27 +3,8 @@
declare(strict_types=1);
/**
- * @copyright 2017 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
@@ -31,11 +12,12 @@ use OC\Security\CSRF\CsrfTokenManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
-#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class CSRFTokenController extends Controller {
public function __construct(
string $appName,
@@ -46,11 +28,19 @@ class CSRFTokenController extends Controller {
}
/**
- * @NoAdminRequired
- * @NoCSRFRequired
- * @PublicPage
+ * Returns a new CSRF token.
+ *
+ * @return JSONResponse<Http::STATUS_OK, array{token: string}, array{}>|JSONResponse<Http::STATUS_FORBIDDEN, list<empty>, array{}>
+ *
+ * 200: CSRF token returned
+ * 403: Strict cookie check failed
+ *
+ * @NoTwoFactorRequired
*/
+ #[PublicPage]
+ #[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/csrftoken')]
+ #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function index(): JSONResponse {
if (!$this->request->passesStrictCookieCheck()) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
diff --git a/core/Controller/ClientFlowLoginController.php b/core/Controller/ClientFlowLoginController.php
index 76079e710e3..4464af890c4 100644
--- a/core/Controller/ClientFlowLoginController.php
+++ b/core/Controller/ClientFlowLoginController.php
@@ -1,55 +1,36 @@
<?php
+
/**
- * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
- *
- * @author Bjoern Schiessle <bjoern@schiessle.org>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Mario Danic <mario@lovelyhq.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author RussellAult <RussellAult@users.noreply.github.com>
- * @author Sergej Nikolaev <kinolaev@gmail.com>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
use OC\Authentication\Events\AppPasswordCreatedEvent;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OC\Authentication\Token\IProvider;
-use OC\Authentication\Token\IToken;
use OCA\OAuth2\Db\AccessToken;
use OCA\OAuth2\Db\AccessTokenMapper;
use OCA\OAuth2\Db\ClientMapper;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\Attribute\UseSession;
+use OCP\AppFramework\Http\ContentSecurityPolicy;
+use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\StandaloneTemplateResponse;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Authentication\Exceptions\InvalidTokenException;
+use OCP\Authentication\Token\IToken;
use OCP\Defaults;
use OCP\EventDispatcher\IEventDispatcher;
+use OCP\IConfig;
use OCP\IL10N;
use OCP\IRequest;
use OCP\ISession;
@@ -79,12 +60,13 @@ class ClientFlowLoginController extends Controller {
private ICrypto $crypto,
private IEventDispatcher $eventDispatcher,
private ITimeFactory $timeFactory,
+ private IConfig $config,
) {
parent::__construct($appName, $request);
}
private function getClientName(): string {
- $userAgent = $this->request->getHeader('USER_AGENT');
+ $userAgent = $this->request->getHeader('user-agent');
return $userAgent !== '' ? $userAgent : 'unknown';
}
@@ -109,13 +91,11 @@ class ClientFlowLoginController extends Controller {
return $response;
}
- /**
- * @PublicPage
- * @NoCSRFRequired
- */
+ #[PublicPage]
+ #[NoCSRFRequired]
#[UseSession]
#[FrontpageRoute(verb: 'GET', url: '/login/flow')]
- public function showAuthPickerPage(string $clientIdentifier = '', string $user = '', int $direct = 0): StandaloneTemplateResponse {
+ public function showAuthPickerPage(string $clientIdentifier = '', string $user = '', int $direct = 0, string $providedRedirectUri = ''): StandaloneTemplateResponse {
$clientName = $this->getClientName();
$client = null;
if ($clientIdentifier !== '') {
@@ -130,8 +110,8 @@ class ClientFlowLoginController extends Controller {
$this->appName,
'error',
[
- 'errors' =>
- [
+ 'errors'
+ => [
[
'error' => 'Access Forbidden',
'hint' => 'Invalid request',
@@ -144,11 +124,11 @@ class ClientFlowLoginController extends Controller {
$stateToken = $this->random->generate(
64,
- ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS
+ ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_DIGITS
);
$this->session->set(self::STATE_NAME, $stateToken);
- $csp = new Http\ContentSecurityPolicy();
+ $csp = new ContentSecurityPolicy();
if ($client) {
$csp->addAllowedFormActionDomain($client->getRedirectUri());
} else {
@@ -168,6 +148,7 @@ class ClientFlowLoginController extends Controller {
'oauthState' => $this->session->get('oauth.state'),
'user' => $user,
'direct' => $direct,
+ 'providedRedirectUri' => $providedRedirectUri,
],
'guest'
);
@@ -177,15 +158,18 @@ class ClientFlowLoginController extends Controller {
}
/**
- * @NoAdminRequired
- * @NoCSRFRequired
* @NoSameSiteCookieRequired
*/
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
#[UseSession]
#[FrontpageRoute(verb: 'GET', url: '/login/flow/grant')]
- public function grantPage(string $stateToken = '',
+ public function grantPage(
+ string $stateToken = '',
string $clientIdentifier = '',
- int $direct = 0): StandaloneTemplateResponse {
+ int $direct = 0,
+ string $providedRedirectUri = '',
+ ): Response {
if (!$this->isValidToken($stateToken)) {
return $this->stateTokenForbiddenResponse();
}
@@ -197,7 +181,7 @@ class ClientFlowLoginController extends Controller {
$clientName = $client->getName();
}
- $csp = new Http\ContentSecurityPolicy();
+ $csp = new ContentSecurityPolicy();
if ($client) {
$csp->addAllowedFormActionDomain($client->getRedirectUri());
} else {
@@ -221,6 +205,7 @@ class ClientFlowLoginController extends Controller {
'serverHost' => $this->getServerPath(),
'oauthState' => $this->session->get('oauth.state'),
'direct' => $direct,
+ 'providedRedirectUri' => $providedRedirectUri,
],
'guest'
);
@@ -229,15 +214,15 @@ class ClientFlowLoginController extends Controller {
return $response;
}
- /**
- * @NoAdminRequired
- *
- * @return Http\RedirectResponse|Response
- */
+ #[NoAdminRequired]
#[UseSession]
+ #[PasswordConfirmationRequired(strict: false)]
#[FrontpageRoute(verb: 'POST', url: '/login/flow')]
- public function generateAppPassword(string $stateToken,
- string $clientIdentifier = '') {
+ public function generateAppPassword(
+ string $stateToken,
+ string $clientIdentifier = '',
+ string $providedRedirectUri = '',
+ ): Response {
if (!$this->isValidToken($stateToken)) {
$this->session->remove(self::STATE_NAME);
return $this->stateTokenForbiddenResponse();
@@ -274,7 +259,7 @@ class ClientFlowLoginController extends Controller {
$clientName = $client->getName();
}
- $token = $this->random->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
+ $token = $this->random->generate(72, ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS);
$uid = $this->userSession->getUser()->getUID();
$generatedToken = $this->tokenProvider->generateToken(
$token,
@@ -287,7 +272,7 @@ class ClientFlowLoginController extends Controller {
);
if ($client) {
- $code = $this->random->generate(128, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
+ $code = $this->random->generate(128, ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS);
$accessToken = new AccessToken();
$accessToken->setClientId($client->getId());
$accessToken->setEncryptedToken($this->crypto->encrypt($token, $code));
@@ -296,7 +281,19 @@ class ClientFlowLoginController extends Controller {
$accessToken->setCodeCreatedAt($this->timeFactory->now()->getTimestamp());
$this->accessTokenMapper->insert($accessToken);
+ $enableOcClients = $this->config->getSystemValueBool('oauth2.enable_oc_clients', false);
+
$redirectUri = $client->getRedirectUri();
+ if ($enableOcClients && $redirectUri === 'http://localhost:*') {
+ // Sanity check untrusted redirect URI provided by the client first
+ if (!preg_match('/^http:\/\/localhost:[0-9]+$/', $providedRedirectUri)) {
+ $response = new Response();
+ $response->setStatus(Http::STATUS_FORBIDDEN);
+ return $response;
+ }
+
+ $redirectUri = $providedRedirectUri;
+ }
if (parse_url($redirectUri, PHP_URL_QUERY)) {
$redirectUri .= '&';
@@ -321,12 +318,10 @@ class ClientFlowLoginController extends Controller {
new AppPasswordCreatedEvent($generatedToken)
);
- return new Http\RedirectResponse($redirectUri);
+ return new RedirectResponse($redirectUri);
}
- /**
- * @PublicPage
- */
+ #[PublicPage]
#[FrontpageRoute(verb: 'POST', url: '/login/flow/apptoken')]
public function apptokenRedirect(string $stateToken, string $user, string $password): Response {
if (!$this->isValidToken($stateToken)) {
@@ -352,7 +347,7 @@ class ClientFlowLoginController extends Controller {
}
$redirectUri = 'nc://login/server:' . $this->getServerPath() . '&user:' . urlencode($user) . '&password:' . urlencode($password);
- return new Http\RedirectResponse($redirectUri);
+ return new RedirectResponse($redirectUri);
}
private function getServerPath(): string {
@@ -366,7 +361,7 @@ class ClientFlowLoginController extends Controller {
$protocol = $this->request->getServerProtocol();
- if ($protocol !== "https") {
+ if ($protocol !== 'https') {
$xForwardedProto = $this->request->getHeader('X-Forwarded-Proto');
$xForwardedSSL = $this->request->getHeader('X-Forwarded-Ssl');
if ($xForwardedProto === 'https' || $xForwardedSSL === 'on') {
@@ -374,6 +369,6 @@ class ClientFlowLoginController extends Controller {
}
}
- return $protocol . "://" . $this->request->getServerHost() . $serverPostfix;
+ return $protocol . '://' . $this->request->getServerHost() . $serverPostfix;
}
}
diff --git a/core/Controller/ClientFlowLoginV2Controller.php b/core/Controller/ClientFlowLoginV2Controller.php
index 19c1f9ce251..8c0c1e8179d 100644
--- a/core/Controller/ClientFlowLoginV2Controller.php
+++ b/core/Controller/ClientFlowLoginV2Controller.php
@@ -3,38 +3,24 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
use OC\Core\Db\LoginFlowV2;
+use OC\Core\Exception\LoginFlowV2ClientForbiddenException;
use OC\Core\Exception\LoginFlowV2NotFoundException;
+use OC\Core\ResponseDefinitions;
use OC\Core\Service\LoginFlowV2Service;
-use OCA\Core\ResponseDefinitions;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\Attribute\UseSession;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\RedirectResponse;
@@ -49,6 +35,7 @@ use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserSession;
use OCP\Security\ISecureRandom;
+use OCP\Server;
/**
* @psalm-import-type CoreLoginFlowV2Credentials from ResponseDefinitions
@@ -57,6 +44,8 @@ use OCP\Security\ISecureRandom;
class ClientFlowLoginV2Controller extends Controller {
public const TOKEN_NAME = 'client.flow.v2.login.token';
public const STATE_NAME = 'client.flow.v2.state.token';
+ // Denotes that the session was created for the login flow and should therefore be ephemeral.
+ public const EPHEMERAL_NAME = 'client.flow.v2.state.ephemeral';
public function __construct(
string $appName,
@@ -74,18 +63,18 @@ class ClientFlowLoginV2Controller extends Controller {
}
/**
- * @NoCSRFRequired
- * @PublicPage
- *
* Poll the login flow credentials
*
* @param string $token Token of the flow
- * @return JSONResponse<Http::STATUS_OK, CoreLoginFlowV2Credentials, array{}>|JSONResponse<Http::STATUS_NOT_FOUND, array<empty>, array{}>
+ * @return JSONResponse<Http::STATUS_OK, CoreLoginFlowV2Credentials, array{}>|JSONResponse<Http::STATUS_NOT_FOUND, list<empty>, array{}>
*
* 200: Login flow credentials returned
* 404: Login flow not found or completed
*/
+ #[NoCSRFRequired]
+ #[PublicPage]
#[FrontpageRoute(verb: 'POST', url: '/login/v2/poll')]
+ #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function poll(string $token): JSONResponse {
try {
$creds = $this->loginFlowV2Service->poll($token);
@@ -96,14 +85,12 @@ class ClientFlowLoginV2Controller extends Controller {
return new JSONResponse($creds->jsonSerialize());
}
- /**
- * @NoCSRFRequired
- * @PublicPage
- */
+ #[NoCSRFRequired]
+ #[PublicPage]
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
#[UseSession]
#[FrontpageRoute(verb: 'GET', url: '/login/v2/flow/{token}')]
- public function landing(string $token, $user = ''): Response {
+ public function landing(string $token, $user = '', int $direct = 0): Response {
if (!$this->loginFlowV2Service->startLoginFlow($token)) {
return $this->loginTokenForbiddenResponse();
}
@@ -111,27 +98,27 @@ class ClientFlowLoginV2Controller extends Controller {
$this->session->set(self::TOKEN_NAME, $token);
return new RedirectResponse(
- $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.showAuthPickerPage', ['user' => $user])
+ $this->urlGenerator->linkToRouteAbsolute('core.ClientFlowLoginV2.showAuthPickerPage', ['user' => $user, 'direct' => $direct])
);
}
- /**
- * @NoCSRFRequired
- * @PublicPage
- */
+ #[NoCSRFRequired]
+ #[PublicPage]
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
#[UseSession]
#[FrontpageRoute(verb: 'GET', url: '/login/v2/flow')]
- public function showAuthPickerPage($user = ''): StandaloneTemplateResponse {
+ public function showAuthPickerPage(string $user = '', int $direct = 0): StandaloneTemplateResponse {
try {
$flow = $this->getFlowByLoginToken();
} catch (LoginFlowV2NotFoundException $e) {
return $this->loginTokenForbiddenResponse();
+ } catch (LoginFlowV2ClientForbiddenException $e) {
+ return $this->loginTokenForbiddenClientResponse();
}
$stateToken = $this->random->generate(
64,
- ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS
+ ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_DIGITS
);
$this->session->set(self::STATE_NAME, $stateToken);
@@ -144,20 +131,21 @@ class ClientFlowLoginV2Controller extends Controller {
'urlGenerator' => $this->urlGenerator,
'stateToken' => $stateToken,
'user' => $user,
+ 'direct' => $direct,
],
'guest'
);
}
/**
- * @NoAdminRequired
- * @NoCSRFRequired
* @NoSameSiteCookieRequired
*/
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
#[UseSession]
#[FrontpageRoute(verb: 'GET', url: '/login/v2/grant')]
- public function grantPage(?string $stateToken): StandaloneTemplateResponse {
+ public function grantPage(?string $stateToken, int $direct = 0): StandaloneTemplateResponse {
if ($stateToken === null) {
return $this->stateTokenMissingResponse();
}
@@ -169,6 +157,8 @@ class ClientFlowLoginV2Controller extends Controller {
$flow = $this->getFlowByLoginToken();
} catch (LoginFlowV2NotFoundException $e) {
return $this->loginTokenForbiddenResponse();
+ } catch (LoginFlowV2ClientForbiddenException $e) {
+ return $this->loginTokenForbiddenClientResponse();
}
/** @var IUser $user */
@@ -184,14 +174,13 @@ class ClientFlowLoginV2Controller extends Controller {
'instanceName' => $this->defaults->getName(),
'urlGenerator' => $this->urlGenerator,
'stateToken' => $stateToken,
+ 'direct' => $direct,
],
'guest'
);
}
- /**
- * @PublicPage
- */
+ #[PublicPage]
#[FrontpageRoute(verb: 'POST', url: '/login/v2/apptoken')]
public function apptokenRedirect(?string $stateToken, string $user, string $password) {
if ($stateToken === null) {
@@ -206,6 +195,8 @@ class ClientFlowLoginV2Controller extends Controller {
$this->getFlowByLoginToken();
} catch (LoginFlowV2NotFoundException $e) {
return $this->loginTokenForbiddenResponse();
+ } catch (LoginFlowV2ClientForbiddenException $e) {
+ return $this->loginTokenForbiddenClientResponse();
}
$loginToken = $this->session->get(self::TOKEN_NAME);
@@ -215,7 +206,7 @@ class ClientFlowLoginV2Controller extends Controller {
$this->session->remove(self::STATE_NAME);
try {
- $token = \OC::$server->get(\OC\Authentication\Token\IProvider::class)->getToken($password);
+ $token = Server::get(\OC\Authentication\Token\IProvider::class)->getToken($password);
if ($token->getLoginName() !== $user) {
throw new InvalidTokenException('login name does not match');
}
@@ -236,10 +227,9 @@ class ClientFlowLoginV2Controller extends Controller {
return $this->handleFlowDone($result);
}
- /**
- * @NoAdminRequired
- */
+ #[NoAdminRequired]
#[UseSession]
+ #[PasswordConfirmationRequired(strict: false)]
#[FrontpageRoute(verb: 'POST', url: '/login/v2/grant')]
public function generateAppPassword(?string $stateToken): Response {
if ($stateToken === null) {
@@ -253,6 +243,8 @@ class ClientFlowLoginV2Controller extends Controller {
$this->getFlowByLoginToken();
} catch (LoginFlowV2NotFoundException $e) {
return $this->loginTokenForbiddenResponse();
+ } catch (LoginFlowV2ClientForbiddenException $e) {
+ return $this->loginTokenForbiddenClientResponse();
}
$loginToken = $this->session->get(self::TOKEN_NAME);
@@ -289,19 +281,19 @@ class ClientFlowLoginV2Controller extends Controller {
}
/**
- * @NoCSRFRequired
- * @PublicPage
- *
* Init a login flow
*
* @return JSONResponse<Http::STATUS_OK, CoreLoginFlowV2, array{}>
*
* 200: Login flow init returned
*/
+ #[NoCSRFRequired]
+ #[PublicPage]
#[FrontpageRoute(verb: 'POST', url: '/login/v2')]
+ #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function init(): JSONResponse {
// Get client user agent
- $userAgent = $this->request->getHeader('USER_AGENT');
+ $userAgent = $this->request->getHeader('user-agent');
$tokens = $this->loginFlowV2Service->createTokens($userAgent);
@@ -353,6 +345,7 @@ class ClientFlowLoginV2Controller extends Controller {
/**
* @return LoginFlowV2
* @throws LoginFlowV2NotFoundException
+ * @throws LoginFlowV2ClientForbiddenException
*/
private function getFlowByLoginToken(): LoginFlowV2 {
$currentToken = $this->session->get(self::TOKEN_NAME);
@@ -376,6 +369,19 @@ class ClientFlowLoginV2Controller extends Controller {
return $response;
}
+ private function loginTokenForbiddenClientResponse(): StandaloneTemplateResponse {
+ $response = new StandaloneTemplateResponse(
+ $this->appName,
+ '403',
+ [
+ 'message' => $this->l10n->t('Please use original client'),
+ ],
+ 'guest'
+ );
+ $response->setStatus(Http::STATUS_FORBIDDEN);
+ return $response;
+ }
+
private function getServerPath(): string {
$serverPostfix = '';
diff --git a/core/Controller/CollaborationResourcesController.php b/core/Controller/CollaborationResourcesController.php
index 9d7d7148468..e160d733176 100644
--- a/core/Controller/CollaborationResourcesController.php
+++ b/core/Controller/CollaborationResourcesController.php
@@ -3,36 +3,17 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2018 Joas Schilling <coding@schilljs.com>
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
use Exception;
-use OCA\Core\ResponseDefinitions;
+use OC\Core\ResponseDefinitions;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\Collaboration\Resources\CollectionException;
@@ -75,16 +56,15 @@ class CollaborationResourcesController extends OCSController {
}
/**
- * @NoAdminRequired
- *
* Get a collection
*
* @param int $collectionId ID of the collection
- * @return DataResponse<Http::STATUS_OK, CoreCollection, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array<empty>, array{}>
+ * @return DataResponse<Http::STATUS_OK, CoreCollection, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, list<empty>, array{}>
*
* 200: Collection returned
* 404: Collection not found
*/
+ #[NoAdminRequired]
#[ApiRoute(verb: 'GET', url: '/resources/collections/{collectionId}', root: '/collaboration')]
public function listCollection(int $collectionId): DataResponse {
try {
@@ -97,16 +77,15 @@ class CollaborationResourcesController extends OCSController {
}
/**
- * @NoAdminRequired
- *
* Search for collections
*
* @param string $filter Filter collections
- * @return DataResponse<Http::STATUS_OK, CoreCollection[], array{}>|DataResponse<Http::STATUS_NOT_FOUND, array<empty>, array{}>
+ * @return DataResponse<Http::STATUS_OK, list<CoreCollection>, array{}>|DataResponse<Http::STATUS_NOT_FOUND, list<empty>, array{}>
*
* 200: Collections returned
* 404: Collection not found
*/
+ #[NoAdminRequired]
#[ApiRoute(verb: 'GET', url: '/resources/collections/search/{filter}', root: '/collaboration')]
public function searchCollections(string $filter): DataResponse {
try {
@@ -119,18 +98,17 @@ class CollaborationResourcesController extends OCSController {
}
/**
- * @NoAdminRequired
- *
* Add a resource to a collection
*
* @param int $collectionId ID of the collection
* @param string $resourceType Name of the resource
* @param string $resourceId ID of the resource
- * @return DataResponse<Http::STATUS_OK, CoreCollection, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array<empty>, array{}>
+ * @return DataResponse<Http::STATUS_OK, CoreCollection, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, list<empty>, array{}>
*
* 200: Collection returned
* 404: Collection not found or resource inaccessible
*/
+ #[NoAdminRequired]
#[ApiRoute(verb: 'POST', url: '/resources/collections/{collectionId}', root: '/collaboration')]
public function addResource(int $collectionId, string $resourceType, string $resourceId): DataResponse {
try {
@@ -154,18 +132,17 @@ class CollaborationResourcesController extends OCSController {
}
/**
- * @NoAdminRequired
- *
* Remove a resource from a collection
*
* @param int $collectionId ID of the collection
* @param string $resourceType Name of the resource
* @param string $resourceId ID of the resource
- * @return DataResponse<Http::STATUS_OK, CoreCollection, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array<empty>, array{}>
+ * @return DataResponse<Http::STATUS_OK, CoreCollection, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, list<empty>, array{}>
*
* 200: Collection returned
* 404: Collection or resource not found
*/
+ #[NoAdminRequired]
#[ApiRoute(verb: 'DELETE', url: '/resources/collections/{collectionId}', root: '/collaboration')]
public function removeResource(int $collectionId, string $resourceType, string $resourceId): DataResponse {
try {
@@ -186,17 +163,16 @@ class CollaborationResourcesController extends OCSController {
}
/**
- * @NoAdminRequired
- *
* Get collections by resource
*
* @param string $resourceType Type of the resource
* @param string $resourceId ID of the resource
- * @return DataResponse<Http::STATUS_OK, CoreCollection[], array{}>|DataResponse<Http::STATUS_NOT_FOUND, array<empty>, array{}>
+ * @return DataResponse<Http::STATUS_OK, list<CoreCollection>, array{}>|DataResponse<Http::STATUS_NOT_FOUND, list<empty>, array{}>
*
* 200: Collections returned
* 404: Resource not accessible
*/
+ #[NoAdminRequired]
#[ApiRoute(verb: 'GET', url: '/resources/{resourceType}/{resourceId}', root: '/collaboration')]
public function getCollectionsByResource(string $resourceType, string $resourceId): DataResponse {
try {
@@ -213,19 +189,18 @@ class CollaborationResourcesController extends OCSController {
}
/**
- * @NoAdminRequired
- *
* Create a collection for a resource
*
* @param string $baseResourceType Type of the base resource
* @param string $baseResourceId ID of the base resource
* @param string $name Name of the collection
- * @return DataResponse<Http::STATUS_OK, CoreCollection, array{}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array<empty>, array{}>
+ * @return DataResponse<Http::STATUS_OK, CoreCollection, array{}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, list<empty>, array{}>
*
* 200: Collection returned
* 400: Creating collection is not possible
* 404: Resource inaccessible
*/
+ #[NoAdminRequired]
#[ApiRoute(verb: 'POST', url: '/resources/{baseResourceType}/{baseResourceId}', root: '/collaboration')]
public function createCollectionOnResource(string $baseResourceType, string $baseResourceId, string $name): DataResponse {
if (!isset($name[0]) || isset($name[64])) {
@@ -249,17 +224,16 @@ class CollaborationResourcesController extends OCSController {
}
/**
- * @NoAdminRequired
- *
* Rename a collection
*
* @param int $collectionId ID of the collection
* @param string $collectionName New name
- * @return DataResponse<Http::STATUS_OK, CoreCollection, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array<empty>, array{}>
+ * @return DataResponse<Http::STATUS_OK, CoreCollection, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, list<empty>, array{}>
*
* 200: Collection returned
* 404: Collection not found
*/
+ #[NoAdminRequired]
#[ApiRoute(verb: 'PUT', url: '/resources/collections/{collectionId}', root: '/collaboration')]
public function renameCollection(int $collectionId, string $collectionName): DataResponse {
try {
@@ -274,7 +248,7 @@ class CollaborationResourcesController extends OCSController {
}
/**
- * @return DataResponse<Http::STATUS_OK, CoreCollection, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array<empty>, array{}>
+ * @return DataResponse<Http::STATUS_OK, CoreCollection, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, list<empty>, array{}>
*/
protected function respondCollection(ICollection $collection): DataResponse {
try {
@@ -288,7 +262,7 @@ class CollaborationResourcesController extends OCSController {
}
/**
- * @return CoreCollection[]
+ * @return list<CoreCollection>
*/
protected function prepareCollections(array $collections): array {
$result = [];
@@ -321,7 +295,7 @@ class CollaborationResourcesController extends OCSController {
}
/**
- * @return CoreResource[]
+ * @return list<CoreResource>
*/
protected function prepareResources(array $resources): array {
$result = [];
diff --git a/core/Controller/ContactsMenuController.php b/core/Controller/ContactsMenuController.php
index e70349970a3..d90ee8a1c61 100644
--- a/core/Controller/ContactsMenuController.php
+++ b/core/Controller/ContactsMenuController.php
@@ -1,26 +1,8 @@
<?php
+
/**
- * @copyright 2017 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Georg Ehrke <oc.list@georgehrke.com>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
@@ -29,6 +11,7 @@ use OC\Contacts\ContactsMenu\Manager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
use OCP\IUserSession;
@@ -43,22 +26,20 @@ class ContactsMenuController extends Controller {
}
/**
- * @NoAdminRequired
- *
* @return \JsonSerializable[]
* @throws Exception
*/
+ #[NoAdminRequired]
#[FrontpageRoute(verb: 'POST', url: '/contactsmenu/contacts')]
public function index(?string $filter = null): array {
return $this->manager->getEntries($this->userSession->getUser(), $filter);
}
/**
- * @NoAdminRequired
- *
* @return JSONResponse|\JsonSerializable
* @throws Exception
*/
+ #[NoAdminRequired]
#[FrontpageRoute(verb: 'POST', url: '/contactsmenu/findOne')]
public function findOne(int $shareType, string $shareWith) {
$contact = $this->manager->findOne($this->userSession->getUser(), $shareType, $shareWith);
diff --git a/core/Controller/CssController.php b/core/Controller/CssController.php
index 3fd0c524b06..37e7edc530f 100644
--- a/core/Controller/CssController.php
+++ b/core/Controller/CssController.php
@@ -3,31 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2016, John Molakvoæ (skjnldsv@protonmail.com)
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Citharel <nextcloud@tcit.fr>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
@@ -35,7 +12,9 @@ use OC\Files\AppData\Factory;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\Http\Response;
@@ -62,14 +41,14 @@ class CssController extends Controller {
}
/**
- * @PublicPage
- * @NoCSRFRequired
* @NoSameSiteCookieRequired
*
* @param string $fileName css filename with extension
* @param string $appName css folder name
* @return FileDisplayResponse|NotFoundResponse
*/
+ #[PublicPage]
+ #[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/css/{appName}/{fileName}')]
public function getCss(string $fileName, string $appName): Response {
try {
@@ -86,11 +65,11 @@ class CssController extends Controller {
}
$ttl = 31536000;
- $response->addHeader('Cache-Control', 'max-age='.$ttl.', immutable');
+ $response->addHeader('Cache-Control', 'max-age=' . $ttl . ', immutable');
$expires = new \DateTime();
$expires->setTimestamp($this->timeFactory->getTime());
- $expires->add(new \DateInterval('PT'.$ttl.'S'));
+ $expires->add(new \DateInterval('PT' . $ttl . 'S'));
$response->addHeader('Expires', $expires->format(\DateTime::RFC1123));
return $response;
}
diff --git a/core/Controller/ErrorController.php b/core/Controller/ErrorController.php
index 040b75be87b..d80dc3f76eb 100644
--- a/core/Controller/ErrorController.php
+++ b/core/Controller/ErrorController.php
@@ -3,41 +3,24 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2022 Julius Härtl <jus@bitgrid.net>
- *
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
+use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\TemplateResponse;
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
-class ErrorController extends \OCP\AppFramework\Controller {
- /**
- * @PublicPage
- * @NoCSRFRequired
- */
+class ErrorController extends Controller {
+ #[PublicPage]
+ #[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: 'error/403')]
public function error403(): TemplateResponse {
$response = new TemplateResponse(
@@ -50,10 +33,8 @@ class ErrorController extends \OCP\AppFramework\Controller {
return $response;
}
- /**
- * @PublicPage
- * @NoCSRFRequired
- */
+ #[PublicPage]
+ #[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: 'error/404')]
public function error404(): TemplateResponse {
$response = new TemplateResponse(
diff --git a/core/Controller/GuestAvatarController.php b/core/Controller/GuestAvatarController.php
index 5e6f2438dd6..711158e0708 100644
--- a/core/Controller/GuestAvatarController.php
+++ b/core/Controller/GuestAvatarController.php
@@ -1,31 +1,17 @@
<?php
+
/**
- * @copyright Copyright (c) 2019, Michael Weimann <mail@michael-weimann.eu>
- *
- * @author Michael Weimann <mail@michael-weimann.eu>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\AppFramework\Http\Response;
use OCP\IAvatarManager;
@@ -51,20 +37,19 @@ class GuestAvatarController extends Controller {
/**
* Returns a guest avatar image response
*
- * @PublicPage
- * @NoCSRFRequired
- *
* @param string $guestName The guest name, e.g. "Albert"
- * @param string $size The desired avatar size, e.g. 64 for 64x64px
+ * @param 64|512 $size The desired avatar size, e.g. 64 for 64x64px
* @param bool|null $darkTheme Return dark avatar
- * @return FileDisplayResponse<Http::STATUS_OK|Http::STATUS_CREATED, array{Content-Type: string}>|Response<Http::STATUS_INTERNAL_SERVER_ERROR, array{}>
+ * @return FileDisplayResponse<Http::STATUS_OK|Http::STATUS_CREATED, array{Content-Type: string, X-NC-IsCustomAvatar: int}>|Response<Http::STATUS_INTERNAL_SERVER_ERROR, array{}>
*
* 200: Custom avatar returned
* 201: Avatar returned
*/
+ #[PublicPage]
+ #[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/avatar/guest/{guestName}/{size}')]
- public function getAvatar(string $guestName, string $size, ?bool $darkTheme = false) {
- $size = (int) $size;
+ #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
+ public function getAvatar(string $guestName, int $size, ?bool $darkTheme = false) {
$darkTheme = $darkTheme ?? false;
if ($size <= 64) {
@@ -86,13 +71,13 @@ class GuestAvatarController extends Controller {
$resp = new FileDisplayResponse(
$avatarFile,
$avatar->isCustomAvatar() ? Http::STATUS_OK : Http::STATUS_CREATED,
- ['Content-Type' => $avatarFile->getMimeType()]
+ ['Content-Type' => $avatarFile->getMimeType(), 'X-NC-IsCustomAvatar' => (int)$avatar->isCustomAvatar()]
);
} catch (\Exception $e) {
$this->logger->error('error while creating guest avatar', [
'err' => $e,
]);
- $resp = new Http\Response();
+ $resp = new Response();
$resp->setStatus(Http::STATUS_INTERNAL_SERVER_ERROR);
return $resp;
}
@@ -105,18 +90,18 @@ class GuestAvatarController extends Controller {
/**
* Returns a dark guest avatar image response
*
- * @PublicPage
- * @NoCSRFRequired
- *
* @param string $guestName The guest name, e.g. "Albert"
- * @param string $size The desired avatar size, e.g. 64 for 64x64px
- * @return FileDisplayResponse<Http::STATUS_OK|Http::STATUS_CREATED, array{Content-Type: string}>|Response<Http::STATUS_INTERNAL_SERVER_ERROR, array{}>
+ * @param 64|512 $size The desired avatar size, e.g. 64 for 64x64px
+ * @return FileDisplayResponse<Http::STATUS_OK|Http::STATUS_CREATED, array{Content-Type: string, X-NC-IsCustomAvatar: int}>|Response<Http::STATUS_INTERNAL_SERVER_ERROR, array{}>
*
* 200: Custom avatar returned
* 201: Avatar returned
*/
+ #[PublicPage]
+ #[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/avatar/guest/{guestName}/{size}/dark')]
- public function getAvatarDark(string $guestName, string $size) {
+ #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
+ public function getAvatarDark(string $guestName, int $size) {
return $this->getAvatar($guestName, $size, true);
}
}
diff --git a/core/Controller/HoverCardController.php b/core/Controller/HoverCardController.php
index 705d506057a..236a81760ac 100644
--- a/core/Controller/HoverCardController.php
+++ b/core/Controller/HoverCardController.php
@@ -2,34 +2,18 @@
declare(strict_types=1);
/**
- * @copyright 2021 Joas Schilling <coding@schilljs.com>
- *
- * @author Joas Schilling <coding@schilljs.com>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
use OC\Contacts\ContactsMenu\Manager;
-use OCA\Core\ResponseDefinitions;
+use OC\Core\ResponseDefinitions;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCSController;
use OCP\IRequest;
use OCP\IUserSession;
use OCP\Share\IShare;
@@ -37,7 +21,7 @@ use OCP\Share\IShare;
/**
* @psalm-import-type CoreContactsAction from ResponseDefinitions
*/
-class HoverCardController extends \OCP\AppFramework\OCSController {
+class HoverCardController extends OCSController {
public function __construct(
IRequest $request,
private IUserSession $userSession,
@@ -47,16 +31,15 @@ class HoverCardController extends \OCP\AppFramework\OCSController {
}
/**
- * @NoAdminRequired
- *
* Get the account details for a hovercard
*
* @param string $userId ID of the user
- * @return DataResponse<Http::STATUS_OK, array{userId: string, displayName: string, actions: CoreContactsAction[]}, array{}>|DataResponse<Http::STATUS_NOT_FOUND, array<empty>, array{}>
+ * @return DataResponse<Http::STATUS_OK, array{userId: string, displayName: string, actions: list<CoreContactsAction>}, array{}>|DataResponse<Http::STATUS_NOT_FOUND, list<empty>, array{}>
*
* 200: Account details returned
* 404: Account not found
*/
+ #[NoAdminRequired]
#[ApiRoute(verb: 'GET', url: '/v1/{userId}', root: '/hovercard')]
public function getUser(string $userId): DataResponse {
$contact = $this->manager->findOne($this->userSession->getUser(), IShare::TYPE_USER, $userId);
@@ -72,7 +55,7 @@ class HoverCardController extends \OCP\AppFramework\OCSController {
array_unshift($actions, $data['topAction']);
}
- /** @var CoreContactsAction[] $actions */
+ /** @var list<CoreContactsAction> $actions */
return new DataResponse([
'userId' => $userId,
'displayName' => $contact->getFullName(),
diff --git a/core/Controller/JsController.php b/core/Controller/JsController.php
index 1f504e05ed0..5754c554e50 100644
--- a/core/Controller/JsController.php
+++ b/core/Controller/JsController.php
@@ -3,31 +3,8 @@
declare(strict_types=1);
/**
- * @copyright 2017, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Citharel <nextcloud@tcit.fr>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
@@ -35,7 +12,9 @@ use OC\Files\AppData\Factory;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\Http\Response;
@@ -62,14 +41,14 @@ class JsController extends Controller {
}
/**
- * @PublicPage
- * @NoCSRFRequired
* @NoSameSiteCookieRequired
*
* @param string $fileName js filename with extension
* @param string $appName js folder name
* @return FileDisplayResponse|NotFoundResponse
*/
+ #[PublicPage]
+ #[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/js/{appName}/{fileName}')]
public function getJs(string $fileName, string $appName): Response {
try {
@@ -86,11 +65,11 @@ class JsController extends Controller {
}
$ttl = 31536000;
- $response->addHeader('Cache-Control', 'max-age='.$ttl.', immutable');
+ $response->addHeader('Cache-Control', 'max-age=' . $ttl . ', immutable');
$expires = new \DateTime();
$expires->setTimestamp($this->timeFactory->getTime());
- $expires->add(new \DateInterval('PT'.$ttl.'S'));
+ $expires->add(new \DateInterval('PT' . $ttl . 'S'));
$response->addHeader('Expires', $expires->format(\DateTime::RFC1123));
return $response;
}
diff --git a/core/Controller/LoginController.php b/core/Controller/LoginController.php
index f22fee4f5e7..5a21d27898f 100644
--- a/core/Controller/LoginController.php
+++ b/core/Controller/LoginController.php
@@ -3,35 +3,9 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2017, Sandro Lutz <sandro.lutz@temparus.ch>
- * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Michael Weimann <mail@michael-weimann.eu>
- * @author Rayn0r <andrew@ilpss8.myfirewall.org>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Controller;
@@ -46,12 +20,16 @@ use OCA\User_LDAP\Helper;
use OCP\App\IAppManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\BruteForceProtection;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\Attribute\UseSession;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\RedirectResponse;
+use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\Defaults;
@@ -64,12 +42,15 @@ use OCP\IUser;
use OCP\IUserManager;
use OCP\Notification\IManager;
use OCP\Security\Bruteforce\IThrottler;
+use OCP\Security\ITrustedDomainHelper;
+use OCP\Server;
use OCP\Util;
class LoginController extends Controller {
public const LOGIN_MSG_INVALIDPASSWORD = 'invalidpassword';
public const LOGIN_MSG_USERDISABLED = 'userdisabled';
public const LOGIN_MSG_CSRFCHECKFAILED = 'csrfCheckFailed';
+ public const LOGIN_MSG_INVALID_ORIGIN = 'invalidOrigin';
public function __construct(
?string $appName,
@@ -91,10 +72,9 @@ class LoginController extends Controller {
}
/**
- * @NoAdminRequired
- *
* @return RedirectResponse
*/
+ #[NoAdminRequired]
#[UseSession]
#[FrontpageRoute(verb: 'GET', url: '/logout')]
public function logout() {
@@ -113,8 +93,8 @@ class LoginController extends Controller {
$this->session->close();
if (
- $this->request->getServerProtocol() === 'https' &&
- !$this->request->isUserAgent([Request::USER_AGENT_CHROME, Request::USER_AGENT_ANDROID_MOBILE_CHROME])
+ $this->request->getServerProtocol() === 'https'
+ && !$this->request->isUserAgent([Request::USER_AGENT_CHROME, Request::USER_AGENT_ANDROID_MOBILE_CHROME])
) {
$response->addHeader('Clear-Site-Data', '"cache", "storage"');
}
@@ -123,18 +103,17 @@ class LoginController extends Controller {
}
/**
- * @PublicPage
- * @NoCSRFRequired
- *
* @param string $user
* @param string $redirect_url
*
* @return TemplateResponse|RedirectResponse
*/
+ #[NoCSRFRequired]
+ #[PublicPage]
#[UseSession]
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
#[FrontpageRoute(verb: 'GET', url: '/login')]
- public function showLoginForm(?string $user = null, ?string $redirect_url = null): Http\Response {
+ public function showLoginForm(?string $user = null, ?string $redirect_url = null): Response {
if ($this->userSession->isLoggedIn()) {
return new RedirectResponse($this->urlGenerator->linkToDefaultPageUrl());
}
@@ -192,6 +171,9 @@ class LoginController extends Controller {
Util::addHeader('meta', ['property' => 'og:type', 'content' => 'website']);
Util::addHeader('meta', ['property' => 'og:image', 'content' => $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'favicon-touch.png'))]);
+ // Add same-origin referrer policy so we can check for valid requests
+ Util::addHeader('meta', ['name' => 'referrer', 'content' => 'same-origin']);
+
$parameters = [
'alt_login' => OC_App::getAlternativeLogIns(),
'pageTitle' => $this->l10n->t('Login'),
@@ -233,7 +215,7 @@ class LoginController extends Controller {
$this->canResetPassword($passwordLink, $user)
);
}
-
+
/**
* Sets the initial state of whether or not a user is allowed to login with their email
* initial state is passed in the array of 1 for email allowed and 0 for not allowed
@@ -244,7 +226,7 @@ class LoginController extends Controller {
// check if user_ldap is enabled, and the required classes exist
if ($this->appManager->isAppLoaded('user_ldap')
&& class_exists(Helper::class)) {
- $helper = \OCP\Server::get(Helper::class);
+ $helper = Server::get(Helper::class);
$allPrefixes = $helper->getServerConfigurationPrefixes();
// check each LDAP server the user is connected too
foreach ($allPrefixes as $prefix) {
@@ -294,30 +276,42 @@ class LoginController extends Controller {
return new RedirectResponse($this->urlGenerator->linkToDefaultPageUrl());
}
- /**
- * @PublicPage
- * @NoCSRFRequired
- * @BruteForceProtection(action=login)
- *
- * @return RedirectResponse
- */
+ #[NoCSRFRequired]
+ #[PublicPage]
+ #[BruteForceProtection(action: 'login')]
#[UseSession]
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
#[FrontpageRoute(verb: 'POST', url: '/login')]
- public function tryLogin(Chain $loginChain,
+ public function tryLogin(
+ Chain $loginChain,
+ ITrustedDomainHelper $trustedDomainHelper,
string $user = '',
string $password = '',
?string $redirect_url = null,
string $timezone = '',
- string $timezone_offset = ''): RedirectResponse {
- if (!$this->request->passesCSRFCheck()) {
+ string $timezone_offset = '',
+ ): RedirectResponse {
+ $error = '';
+
+ $origin = $this->request->getHeader('Origin');
+ $throttle = true;
+ if ($origin === '' || !$trustedDomainHelper->isTrustedUrl($origin)) {
+ // Login attempt not from the same origin,
+ // We only allow this on the login flow but not on the UI login page.
+ // This could have come from someone malicious who tries to block a user by triggering the bruteforce protection.
+ $error = self::LOGIN_MSG_INVALID_ORIGIN;
+ $throttle = false;
+ } elseif (!$this->request->passesCSRFCheck()) {
if ($this->userSession->isLoggedIn()) {
// If the user is already logged in and the CSRF check does not pass then
// simply redirect the user to the correct page as required. This is the
// case when a user has already logged-in, in another tab.
return $this->generateRedirect($redirect_url);
}
+ $error = self::LOGIN_MSG_CSRFCHECKFAILED;
+ }
+ if ($error !== '') {
// Clear any auth remnants like cookies to ensure a clean login
// For the next attempt
$this->userSession->logout();
@@ -325,7 +319,8 @@ class LoginController extends Controller {
$user,
$user,
$redirect_url,
- self::LOGIN_MSG_CSRFCHECKFAILED
+ $error,
+ $throttle,
);
}
@@ -375,7 +370,12 @@ class LoginController extends Controller {
* @return RedirectResponse
*/
private function createLoginFailedResponse(
- $user, $originalUser, $redirect_url, string $loginMessage) {
+ $user,
+ $originalUser,
+ $redirect_url,
+ string $loginMessage,
+ bool $throttle = true,
+ ) {
// Read current user and append if possible we need to
// return the unmodified user otherwise we will leak the login name
$args = $user !== null ? ['user' => $originalUser, 'direct' => 1] : [];
@@ -385,31 +385,34 @@ class LoginController extends Controller {
$response = new RedirectResponse(
$this->urlGenerator->linkToRoute('core.login.showLoginForm', $args)
);
- $response->throttle(['user' => substr($user, 0, 64)]);
+ if ($throttle) {
+ $response->throttle(['user' => substr($user, 0, 64)]);
+ }
$this->session->set('loginMessages', [
[$loginMessage], []
]);
+
return $response;
}
/**
* Confirm the user password
*
- * @NoAdminRequired
- * @BruteForceProtection(action=sudo)
- *
* @license GNU AGPL version 3 or any later version
*
* @param string $password The password of the user
*
- * @return DataResponse<Http::STATUS_OK, array{lastLogin: int}, array{}>|DataResponse<Http::STATUS_FORBIDDEN, array<empty>, array{}>
+ * @return DataResponse<Http::STATUS_OK, array{lastLogin: int}, array{}>|DataResponse<Http::STATUS_FORBIDDEN, list<empty>, array{}>
*
* 200: Password confirmation succeeded
* 403: Password confirmation failed
*/
+ #[NoAdminRequired]
+ #[BruteForceProtection(action: 'sudo')]
#[UseSession]
#[NoCSRFRequired]
#[FrontpageRoute(verb: 'POST', url: '/login/confirm')]
+ #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function confirmPassword(string $password): DataResponse {
$loginName = $this->userSession->getLoginName();
$loginResult = $this->userManager->checkPassword($loginName, $password);
diff --git a/core/Controller/LostController.php b/core/Controller/LostController.php
index d94386f9ab5..d956f3427f2 100644
--- a/core/Controller/LostController.php
+++ b/core/Controller/LostController.php
@@ -1,38 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Bernhard Posselt <dev@bernhard-posselt.com>
- * @author Bjoern Schiessle <bjoern@schiessle.org>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Julius Haertl <jus@bitgrid.net>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Rémy Jacquin <remy@remyj.fr>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Victor Dubiniuk <dubiniuk@owncloud.com>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Controller;
@@ -43,9 +14,14 @@ use OC\Core\Events\PasswordResetEvent;
use OC\Core\Exception\ResetPasswordException;
use OC\Security\RateLimiting\Exception\RateLimitExceededException;
use OC\Security\RateLimiting\Limiter;
+use OC\User\Session;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\Attribute\AnonRateLimit;
+use OCP\AppFramework\Http\Attribute\BruteForceProtection;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
@@ -61,8 +37,11 @@ use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Mail\IMailer;
+use OCP\PreConditionNotMetException;
use OCP\Security\VerificationToken\InvalidTokenException;
use OCP\Security\VerificationToken\IVerificationToken;
+use OCP\Server;
+use OCP\Util;
use Psr\Log\LoggerInterface;
use function array_filter;
use function count;
@@ -77,8 +56,6 @@ use function reset;
*/
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class LostController extends Controller {
- protected string $from;
-
public function __construct(
string $appName,
IRequest $request,
@@ -87,7 +64,7 @@ class LostController extends Controller {
private Defaults $defaults,
private IL10N $l10n,
private IConfig $config,
- string $defaultMailAddress,
+ protected string $defaultMailAddress,
private IManager $encryptionManager,
private IMailer $mailer,
private LoggerInterface $logger,
@@ -98,17 +75,15 @@ class LostController extends Controller {
private Limiter $limiter,
) {
parent::__construct($appName, $request);
- $this->from = $defaultMailAddress;
}
/**
* Someone wants to reset their password:
- *
- * @PublicPage
- * @NoCSRFRequired
- * @BruteForceProtection(action=passwordResetEmail)
- * @AnonRateThrottle(limit=10, period=300)
*/
+ #[PublicPage]
+ #[NoCSRFRequired]
+ #[BruteForceProtection(action: 'passwordResetEmail')]
+ #[AnonRateLimit(limit: 10, period: 300)]
#[FrontpageRoute(verb: 'GET', url: '/lostpassword/reset/form/{token}/{userId}')]
public function resetform(string $token, string $userId): TemplateResponse {
try {
@@ -120,7 +95,7 @@ class LostController extends Controller {
) {
$response = new TemplateResponse(
'core', 'error', [
- "errors" => [["error" => $e->getMessage()]]
+ 'errors' => [['error' => $e->getMessage()]]
],
TemplateResponse::RENDER_AS_GUEST
);
@@ -169,11 +144,9 @@ class LostController extends Controller {
return array_merge($data, ['status' => 'success']);
}
- /**
- * @PublicPage
- * @BruteForceProtection(action=passwordResetEmail)
- * @AnonRateThrottle(limit=10, period=300)
- */
+ #[PublicPage]
+ #[BruteForceProtection(action: 'passwordResetEmail')]
+ #[AnonRateLimit(limit: 10, period: 300)]
#[FrontpageRoute(verb: 'POST', url: '/lostpassword/email')]
public function email(string $user): JSONResponse {
if ($this->config->getSystemValue('lost_password_link', '') !== '') {
@@ -186,7 +159,7 @@ class LostController extends Controller {
return new JSONResponse($this->error($this->l10n->t('Unsupported email length (>255)')));
}
- \OCP\Util::emitHook(
+ Util::emitHook(
'\OCA\Files_Sharing\API\Server2Server',
'preLoginNameUsedAsUserName',
['uid' => &$user]
@@ -207,11 +180,9 @@ class LostController extends Controller {
return $response;
}
- /**
- * @PublicPage
- * @BruteForceProtection(action=passwordResetEmail)
- * @AnonRateThrottle(limit=10, period=300)
- */
+ #[PublicPage]
+ #[BruteForceProtection(action: 'passwordResetEmail')]
+ #[AnonRateLimit(limit: 10, period: 300)]
#[FrontpageRoute(verb: 'POST', url: '/lostpassword/set/{token}/{userId}')]
public function setPassword(string $token, string $userId, string $password, bool $proceed): JSONResponse {
if ($this->encryptionManager->isEnabled() && !$proceed) {
@@ -247,7 +218,7 @@ class LostController extends Controller {
$this->twoFactorManager->clearTwoFactorPending($userId);
$this->config->deleteUserValue($userId, 'core', 'lostpassword');
- @\OC::$server->getUserSession()->unsetMagicInCookie();
+ @Server::get(Session::class)->unsetMagicInCookie();
} catch (HintException $e) {
$response = new JSONResponse($this->error($e->getHint()));
$response->throttle();
@@ -263,7 +234,7 @@ class LostController extends Controller {
/**
* @throws ResetPasswordException
- * @throws \OCP\PreConditionNotMetException
+ * @throws PreConditionNotMetException
*/
protected function sendEmail(string $input): void {
$user = $this->findUserByIdOrMail($input);
@@ -310,7 +281,7 @@ class LostController extends Controller {
try {
$message = $this->mailer->createMessage();
$message->setTo([$email => $user->getDisplayName()]);
- $message->setFrom([$this->from => $this->defaults->getName()]);
+ $message->setFrom([$this->defaultMailAddress => $this->defaults->getName()]);
$message->useTemplate($emailTemplate);
$this->mailer->send($message);
} catch (Exception $e) {
diff --git a/core/Controller/NavigationController.php b/core/Controller/NavigationController.php
index 7b651e6ec70..017061ef979 100644
--- a/core/Controller/NavigationController.php
+++ b/core/Controller/NavigationController.php
@@ -1,31 +1,16 @@
<?php
+
/**
- * @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
- *
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
-use OCA\Core\ResponseDefinitions;
+use OC\Core\ResponseDefinitions;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\INavigationManager;
@@ -46,17 +31,16 @@ class NavigationController extends OCSController {
}
/**
- * @NoAdminRequired
- * @NoCSRFRequired
- *
* Get the apps navigation
*
* @param bool $absolute Rewrite URLs to absolute ones
- * @return DataResponse<Http::STATUS_OK, CoreNavigationEntry[], array{}>|DataResponse<Http::STATUS_NOT_MODIFIED, array<empty>, array{}>
+ * @return DataResponse<Http::STATUS_OK, list<CoreNavigationEntry>, array{}>|DataResponse<Http::STATUS_NOT_MODIFIED, list<empty>, array{}>
*
* 200: Apps navigation returned
* 304: No apps navigation changed
*/
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
#[ApiRoute(verb: 'GET', url: '/navigation/apps', root: '/core')]
public function getAppsNavigation(bool $absolute = false): DataResponse {
$navigation = $this->navigationManager->getAll();
@@ -64,27 +48,22 @@ class NavigationController extends OCSController {
$navigation = $this->rewriteToAbsoluteUrls($navigation);
}
$navigation = array_values($navigation);
- $etag = $this->generateETag($navigation);
- if ($this->request->getHeader('If-None-Match') === $etag) {
- return new DataResponse([], Http::STATUS_NOT_MODIFIED);
- }
$response = new DataResponse($navigation);
- $response->setETag($etag);
+ $response->setETag($this->generateETag($navigation));
return $response;
}
/**
- * @NoAdminRequired
- * @NoCSRFRequired
- *
* Get the settings navigation
*
* @param bool $absolute Rewrite URLs to absolute ones
- * @return DataResponse<Http::STATUS_OK, CoreNavigationEntry[], array{}>|DataResponse<Http::STATUS_NOT_MODIFIED, array<empty>, array{}>
+ * @return DataResponse<Http::STATUS_OK, list<CoreNavigationEntry>, array{}>|DataResponse<Http::STATUS_NOT_MODIFIED, list<empty>, array{}>
*
* 200: Apps navigation returned
* 304: No apps navigation changed
*/
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
#[ApiRoute(verb: 'GET', url: '/navigation/settings', root: '/core')]
public function getSettingsNavigation(bool $absolute = false): DataResponse {
$navigation = $this->navigationManager->getAll('settings');
@@ -92,12 +71,8 @@ class NavigationController extends OCSController {
$navigation = $this->rewriteToAbsoluteUrls($navigation);
}
$navigation = array_values($navigation);
- $etag = $this->generateETag($navigation);
- if ($this->request->getHeader('If-None-Match') === $etag) {
- return new DataResponse([], Http::STATUS_NOT_MODIFIED);
- }
$response = new DataResponse($navigation);
- $response->setETag($etag);
+ $response->setETag($this->generateETag($navigation));
return $response;
}
diff --git a/core/Controller/OCJSController.php b/core/Controller/OCJSController.php
index dbb203e827f..083ad4b209f 100644
--- a/core/Controller/OCJSController.php
+++ b/core/Controller/OCJSController.php
@@ -1,43 +1,26 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Bjoern Schiessle <bjoern@schiessle.org>
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
use bantu\IniGetWrapper\IniGetWrapper;
+use OC\Authentication\Token\IProvider;
use OC\CapabilitiesManager;
+use OC\Files\FilenameValidator;
use OC\Template\JSConfigHelper;
use OCP\App\IAppManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\DataDisplayResponse;
use OCP\Defaults;
+use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IInitialStateService;
@@ -46,6 +29,7 @@ use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUserSession;
use OCP\L10N\IFactory;
+use OCP\ServerVersion;
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class OCJSController extends Controller {
@@ -60,34 +44,42 @@ class OCJSController extends Controller {
ISession $session,
IUserSession $userSession,
IConfig $config,
+ IAppConfig $appConfig,
IGroupManager $groupManager,
IniGetWrapper $iniWrapper,
IURLGenerator $urlGenerator,
CapabilitiesManager $capabilitiesManager,
IInitialStateService $initialStateService,
+ IProvider $tokenProvider,
+ FilenameValidator $filenameValidator,
+ ServerVersion $serverVersion,
) {
parent::__construct($appName, $request);
$this->helper = new JSConfigHelper(
+ $serverVersion,
$l10nFactory->get('lib'),
$defaults,
$appManager,
$session,
$userSession->getUser(),
$config,
+ $appConfig,
$groupManager,
$iniWrapper,
$urlGenerator,
$capabilitiesManager,
- $initialStateService
+ $initialStateService,
+ $tokenProvider,
+ $filenameValidator,
);
}
/**
- * @NoCSRFRequired
* @NoTwoFactorRequired
- * @PublicPage
*/
+ #[PublicPage]
+ #[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/core/js/oc.js')]
public function getConfig(): DataDisplayResponse {
$data = $this->helper->getConfig();
diff --git a/core/Controller/OCMController.php b/core/Controller/OCMController.php
index 4fa03e67c4f..2d3b99f431d 100644
--- a/core/Controller/OCMController.php
+++ b/core/Controller/OCMController.php
@@ -3,36 +3,23 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2023 Maxence Lange <maxence@artificial-owl.com>
- *
- * @author Maxence Lange <maxence@artificial-owl.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
use Exception;
+use OCA\CloudFederationAPI\Capabilities;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\DataResponse;
use OCP\Capabilities\ICapability;
-use OCP\IConfig;
+use OCP\IAppConfig;
use OCP\IRequest;
use OCP\Server;
use Psr\Container\ContainerExceptionInterface;
@@ -46,8 +33,8 @@ use Psr\Log\LoggerInterface;
class OCMController extends Controller {
public function __construct(
IRequest $request,
- private IConfig $config,
- private LoggerInterface $logger
+ private readonly IAppConfig $appConfig,
+ private LoggerInterface $logger,
) {
parent::__construct('core', $request);
}
@@ -56,23 +43,24 @@ class OCMController extends Controller {
* generate a OCMProvider with local data and send it as DataResponse.
* This replaces the old PHP file ocm-provider/index.php
*
- * @PublicPage
- * @NoCSRFRequired
* @psalm-suppress MoreSpecificReturnType
* @psalm-suppress LessSpecificReturnStatement
- * @return DataResponse<Http::STATUS_OK, array{enabled: bool, apiVersion: string, endPoint: string, resourceTypes: array{name: string, shareTypes: string[], protocols: array{webdav: string}}[]}, array{X-NEXTCLOUD-OCM-PROVIDERS: true, Content-Type: 'application/json'}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
+ * @return DataResponse<Http::STATUS_OK, array{enabled: bool, apiVersion: string, endPoint: string, resourceTypes: list<array{name: string, shareTypes: list<string>, protocols: array{webdav: string}}>}, array{X-NEXTCLOUD-OCM-PROVIDERS: true, Content-Type: 'application/json'}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
*
* 200: OCM Provider details returned
* 500: OCM not supported
*/
+ #[PublicPage]
+ #[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/ocm-provider/')]
+ #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function discovery(): DataResponse {
try {
$cap = Server::get(
- $this->config->getAppValue(
- 'core',
- 'ocm_providers',
- '\OCA\CloudFederationAPI\Capabilities'
+ $this->appConfig->getValueString(
+ 'core', 'ocm_providers',
+ Capabilities::class,
+ lazy: true
)
);
diff --git a/core/Controller/OCSController.php b/core/Controller/OCSController.php
index c6ddc23717d..fb0280479c4 100644
--- a/core/Controller/OCSController.php
+++ b/core/Controller/OCSController.php
@@ -1,30 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
@@ -32,11 +10,15 @@ use OC\CapabilitiesManager;
use OC\Security\IdentityProof\Manager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
+use OCP\AppFramework\Http\Attribute\BruteForceProtection;
use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;
use OCP\IUserManager;
use OCP\IUserSession;
+use OCP\ServerVersion;
+use OCP\Util;
class OCSController extends \OCP\AppFramework\OCSController {
public function __construct(
@@ -46,13 +28,12 @@ class OCSController extends \OCP\AppFramework\OCSController {
private IUserSession $userSession,
private IUserManager $userManager,
private Manager $keyManager,
+ private ServerVersion $serverVersion,
) {
parent::__construct($appName, $request);
}
- /**
- * @PublicPage
- */
+ #[PublicPage]
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
#[ApiRoute(verb: 'GET', url: '/config', root: '')]
public function getConfig(): DataResponse {
@@ -68,25 +49,23 @@ class OCSController extends \OCP\AppFramework\OCSController {
}
/**
- * @PublicPage
- *
* Get the capabilities
*
* @return DataResponse<Http::STATUS_OK, array{version: array{major: int, minor: int, micro: int, string: string, edition: '', extendedSupport: bool}, capabilities: array<string, mixed>}, array{}>
*
* 200: Capabilities returned
*/
+ #[PublicPage]
#[ApiRoute(verb: 'GET', url: '/capabilities', root: '/cloud')]
public function getCapabilities(): DataResponse {
$result = [];
- [$major, $minor, $micro] = \OCP\Util::getVersion();
$result['version'] = [
- 'major' => (int)$major,
- 'minor' => (int)$minor,
- 'micro' => (int)$micro,
- 'string' => \OC_Util::getVersionString(),
+ 'major' => $this->serverVersion->getMajorVersion(),
+ 'minor' => $this->serverVersion->getMinorVersion(),
+ 'micro' => $this->serverVersion->getPatchVersion(),
+ 'string' => $this->serverVersion->getVersionString(),
'edition' => '',
- 'extendedSupport' => \OCP\Util::hasExtendedSupport()
+ 'extendedSupport' => Util::hasExtendedSupport()
];
if ($this->userSession->isLoggedIn()) {
@@ -100,10 +79,8 @@ class OCSController extends \OCP\AppFramework\OCSController {
return $response;
}
- /**
- * @PublicPage
- * @BruteForceProtection(action=login)
- */
+ #[PublicPage]
+ #[BruteForceProtection(action: 'login')]
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
#[ApiRoute(verb: 'POST', url: '/check', root: '/person')]
public function personCheck(string $login = '', string $password = ''): DataResponse {
@@ -123,9 +100,7 @@ class OCSController extends \OCP\AppFramework\OCSController {
return new DataResponse([], 101);
}
- /**
- * @PublicPage
- */
+ #[PublicPage]
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
#[ApiRoute(verb: 'GET', url: '/key/{cloudId}', root: '/identityproof')]
public function getIdentityProof(string $cloudId): DataResponse {
diff --git a/core/Controller/PreviewController.php b/core/Controller/PreviewController.php
index 7ab22dceaa2..aac49c06d57 100644
--- a/core/Controller/PreviewController.php
+++ b/core/Controller/PreviewController.php
@@ -3,41 +3,27 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2016, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
-use OCA\Files_Sharing\SharedStorage;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\AppFramework\Http\RedirectResponse;
+use OCP\AppFramework\Http\Response;
use OCP\Files\File;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
+use OCP\Files\Storage\ISharedStorage;
use OCP\IPreview;
use OCP\IRequest;
use OCP\Preview\IMimeIconProvider;
@@ -55,19 +41,16 @@ class PreviewController extends Controller {
}
/**
- * @NoAdminRequired
- * @NoCSRFRequired
- *
* Get a preview by file path
*
* @param string $file Path of the file
- * @param int $x Width of the preview
- * @param int $y Height of the preview
- * @param bool $a Whether to not crop the preview
+ * @param int $x Width of the preview. A width of -1 will use the original image width.
+ * @param int $y Height of the preview. A height of -1 will use the original image height.
+ * @param bool $a Preserve the aspect ratio
* @param bool $forceIcon Force returning an icon
- * @param string $mode How to crop the image
+ * @param 'fill'|'cover' $mode How to crop the image
* @param bool $mimeFallback Whether to fallback to the mime icon if no preview is available
- * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>|RedirectResponse<Http::STATUS_SEE_OTHER, array{}>
+ * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, list<empty>, array{}>|RedirectResponse<Http::STATUS_SEE_OTHER, array{}>
*
* 200: Preview returned
* 303: Redirect to the mime icon url if mimeFallback is true
@@ -75,7 +58,10 @@ class PreviewController extends Controller {
* 403: Getting preview is not allowed
* 404: Preview not found
*/
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/core/preview.png')]
+ #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function getPreview(
string $file = '',
int $x = 32,
@@ -83,7 +69,7 @@ class PreviewController extends Controller {
bool $a = false,
bool $forceIcon = true,
string $mode = 'fill',
- bool $mimeFallback = false): Http\Response {
+ bool $mimeFallback = false): Response {
if ($file === '' || $x === 0 || $y === 0) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
@@ -99,19 +85,16 @@ class PreviewController extends Controller {
}
/**
- * @NoAdminRequired
- * @NoCSRFRequired
- *
* Get a preview by file ID
*
* @param int $fileId ID of the file
- * @param int $x Width of the preview
- * @param int $y Height of the preview
- * @param bool $a Whether to not crop the preview
+ * @param int $x Width of the preview. A width of -1 will use the original image width.
+ * @param int $y Height of the preview. A height of -1 will use the original image height.
+ * @param bool $a Preserve the aspect ratio
* @param bool $forceIcon Force returning an icon
- * @param string $mode How to crop the image
+ * @param 'fill'|'cover' $mode How to crop the image
* @param bool $mimeFallback Whether to fallback to the mime icon if no preview is available
- * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>|RedirectResponse<Http::STATUS_SEE_OTHER, array{}>
+ * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, list<empty>, array{}>|RedirectResponse<Http::STATUS_SEE_OTHER, array{}>
*
* 200: Preview returned
* 303: Redirect to the mime icon url if mimeFallback is true
@@ -119,7 +102,10 @@ class PreviewController extends Controller {
* 403: Getting preview is not allowed
* 404: Preview not found
*/
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/core/preview')]
+ #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function getPreviewByFileId(
int $fileId = -1,
int $x = 32,
@@ -143,7 +129,7 @@ class PreviewController extends Controller {
}
/**
- * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>|RedirectResponse<Http::STATUS_SEE_OTHER, array{}>
+ * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, list<empty>, array{}>|RedirectResponse<Http::STATUS_SEE_OTHER, array{}>
*/
private function fetchPreview(
Node $node,
@@ -152,7 +138,7 @@ class PreviewController extends Controller {
bool $a,
bool $forceIcon,
string $mode,
- bool $mimeFallback = false) : Http\Response {
+ bool $mimeFallback = false) : Response {
if (!($node instanceof File) || (!$forceIcon && !$this->preview->isAvailable($node))) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
@@ -160,12 +146,18 @@ class PreviewController extends Controller {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
+ if ($node->getId() <= 0) {
+ return new DataResponse([], Http::STATUS_NOT_FOUND);
+ }
+
+ // Is this header is set it means our UI is doing a preview for no-download shares
+ // we check a header so we at least prevent people from using the link directly (obfuscation)
+ $isNextcloudPreview = $this->request->getHeader('x-nc-preview') === 'true';
$storage = $node->getStorage();
- if ($storage->instanceOfStorage(SharedStorage::class)) {
- /** @var SharedStorage $storage */
+ if ($isNextcloudPreview === false && $storage->instanceOfStorage(ISharedStorage::class)) {
+ /** @var ISharedStorage $storage */
$share = $storage->getShare();
- $attributes = $share->getAttributes();
- if ($attributes !== null && $attributes->getAttribute('permissions', 'download') === false) {
+ if (!$share->canSeeContent()) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
}
@@ -190,4 +182,25 @@ class PreviewController extends Controller {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
}
+
+ /**
+ * Get a preview by mime
+ *
+ * @param string $mime Mime type
+ * @return RedirectResponse<Http::STATUS_SEE_OTHER, array{}>
+ *
+ * 303: The mime icon url
+ */
+ #[NoCSRFRequired]
+ #[PublicPage]
+ #[FrontpageRoute(verb: 'GET', url: '/core/mimeicon')]
+ #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
+ public function getMimeIconUrl(string $mime = 'application/octet-stream') {
+ $url = $this->mimeIconProvider->getMimeIconUrl($mime);
+ if ($url === null) {
+ $url = $this->mimeIconProvider->getMimeIconUrl('application/octet-stream');
+ }
+
+ return new RedirectResponse($url);
+ }
}
diff --git a/core/Controller/ProfileApiController.php b/core/Controller/ProfileApiController.php
index 7cba0593c1f..02979cb1649 100644
--- a/core/Controller/ProfileApiController.php
+++ b/core/Controller/ProfileApiController.php
@@ -3,83 +3,79 @@
declare(strict_types=1);
/**
- * @copyright 2021 Christopher Ng <chrng8@gmail.com>
- *
- * @author Christopher Ng <chrng8@gmail.com>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
use OC\Core\Db\ProfileConfigMapper;
+use OC\Core\ResponseDefinitions;
use OC\Profile\ProfileManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
+use OCP\AppFramework\Http\Attribute\BruteForceProtection;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
+use OCP\AppFramework\Http\Attribute\UserRateLimit;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCS\OCSForbiddenException;
use OCP\AppFramework\OCS\OCSNotFoundException;
use OCP\AppFramework\OCSController;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\IConfig;
use OCP\IRequest;
+use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession;
+use OCP\Share\IManager;
+/**
+ * @psalm-import-type CoreProfileData from ResponseDefinitions
+ */
class ProfileApiController extends OCSController {
public function __construct(
IRequest $request,
+ private IConfig $config,
+ private ITimeFactory $timeFactory,
private ProfileConfigMapper $configMapper,
private ProfileManager $profileManager,
private IUserManager $userManager,
private IUserSession $userSession,
+ private IManager $shareManager,
) {
parent::__construct('core', $request);
}
/**
- * @NoAdminRequired
* @NoSubAdminRequired
- * @PasswordConfirmationRequired
- * @UserRateThrottle(limit=40, period=600)
*
* Update the visibility of a parameter
*
* @param string $targetUserId ID of the user
* @param string $paramId ID of the parameter
* @param string $visibility New visibility
- * @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
+ * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
* @throws OCSBadRequestException Updating visibility is not possible
* @throws OCSForbiddenException Not allowed to edit other users visibility
* @throws OCSNotFoundException Account not found
*
* 200: Visibility updated successfully
*/
+ #[NoAdminRequired]
+ #[PasswordConfirmationRequired]
+ #[UserRateLimit(limit: 40, period: 600)]
#[ApiRoute(verb: 'PUT', url: '/{targetUserId}', root: '/profile')]
public function setVisibility(string $targetUserId, string $paramId, string $visibility): DataResponse {
$requestingUser = $this->userSession->getUser();
- $targetUser = $this->userManager->get($targetUserId);
-
- if (!$this->userManager->userExists($targetUserId)) {
- throw new OCSNotFoundException('Account does not exist');
+ if ($requestingUser->getUID() !== $targetUserId) {
+ throw new OCSForbiddenException('People can only edit their own visibility settings');
}
- if ($requestingUser !== $targetUser) {
- throw new OCSForbiddenException('People can only edit their own visibility settings');
+ $targetUser = $this->userManager->get($targetUserId);
+ if (!$targetUser instanceof IUser) {
+ throw new OCSNotFoundException('Account does not exist');
}
// Ensure that a profile config is created in the database
@@ -95,4 +91,55 @@ class ProfileApiController extends OCSController {
return new DataResponse();
}
+
+ /**
+ * Get profile fields for another user
+ *
+ * @param string $targetUserId ID of the user
+ * @return DataResponse<Http::STATUS_OK, CoreProfileData, array{}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND, null, array{}>
+ *
+ * 200: Profile data returned successfully
+ * 400: Profile is disabled
+ * 404: Account not found or disabled
+ */
+ #[NoAdminRequired]
+ #[ApiRoute(verb: 'GET', url: '/{targetUserId}', root: '/profile')]
+ #[BruteForceProtection(action: 'user')]
+ #[UserRateLimit(limit: 30, period: 120)]
+ public function getProfileFields(string $targetUserId): DataResponse {
+ $targetUser = $this->userManager->get($targetUserId);
+ if (!$targetUser instanceof IUser) {
+ $response = new DataResponse(null, Http::STATUS_NOT_FOUND);
+ $response->throttle();
+ return $response;
+ }
+ if (!$targetUser->isEnabled()) {
+ return new DataResponse(null, Http::STATUS_NOT_FOUND);
+ }
+
+ if (!$this->profileManager->isProfileEnabled($targetUser)) {
+ return new DataResponse(null, Http::STATUS_BAD_REQUEST);
+ }
+
+ $requestingUser = $this->userSession->getUser();
+ if ($targetUser !== $requestingUser) {
+ if (!$this->shareManager->currentUserCanEnumerateTargetUser($requestingUser, $targetUser)) {
+ return new DataResponse(null, Http::STATUS_NOT_FOUND);
+ }
+ }
+
+ $profileFields = $this->profileManager->getProfileFields($targetUser, $requestingUser);
+
+ // Extend the profile information with timezone of the user
+ $timezoneStringTarget = $this->config->getUserValue($targetUser->getUID(), 'core', 'timezone') ?: $this->config->getSystemValueString('default_timezone', 'UTC');
+ try {
+ $timezoneTarget = new \DateTimeZone($timezoneStringTarget);
+ } catch (\Throwable) {
+ $timezoneTarget = new \DateTimeZone('UTC');
+ }
+ $profileFields['timezone'] = $timezoneTarget->getName(); // E.g. Europe/Berlin
+ $profileFields['timezoneOffset'] = $timezoneTarget->getOffset($this->timeFactory->now()); // In seconds E.g. 7200
+
+ return new DataResponse($profileFields);
+ }
}
diff --git a/core/Controller/ProfilePageController.php b/core/Controller/ProfilePageController.php
deleted file mode 100644
index c3a33d6bbda..00000000000
--- a/core/Controller/ProfilePageController.php
+++ /dev/null
@@ -1,126 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * @copyright 2021 Christopher Ng <chrng8@gmail.com>
- *
- * @author Christopher Ng <chrng8@gmail.com>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-namespace OC\Core\Controller;
-
-use OC\Profile\ProfileManager;
-use OCP\AppFramework\Controller;
-use OCP\AppFramework\Http\Attribute\FrontpageRoute;
-use OCP\AppFramework\Http\Attribute\OpenAPI;
-use OCP\AppFramework\Http\TemplateResponse;
-use OCP\AppFramework\Services\IInitialState;
-use OCP\EventDispatcher\IEventDispatcher;
-use OCP\INavigationManager;
-use OCP\IRequest;
-use OCP\IUser;
-use OCP\IUserManager;
-use OCP\IUserSession;
-use OCP\Profile\BeforeTemplateRenderedEvent;
-use OCP\Share\IManager as IShareManager;
-use OCP\UserStatus\IManager as IUserStatusManager;
-
-#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
-class ProfilePageController extends Controller {
- public function __construct(
- string $appName,
- IRequest $request,
- private IInitialState $initialStateService,
- private ProfileManager $profileManager,
- private IShareManager $shareManager,
- private IUserManager $userManager,
- private IUserSession $userSession,
- private IUserStatusManager $userStatusManager,
- private INavigationManager $navigationManager,
- private IEventDispatcher $eventDispatcher,
- ) {
- parent::__construct($appName, $request);
- }
-
- /**
- * @PublicPage
- * @NoCSRFRequired
- * @NoAdminRequired
- * @NoSubAdminRequired
- */
- #[FrontpageRoute(verb: 'GET', url: '/u/{targetUserId}')]
- public function index(string $targetUserId): TemplateResponse {
- $profileNotFoundTemplate = new TemplateResponse(
- 'core',
- '404-profile',
- [],
- TemplateResponse::RENDER_AS_GUEST,
- );
-
- $targetUser = $this->userManager->get($targetUserId);
- if (!($targetUser instanceof IUser) || !$targetUser->isEnabled()) {
- return $profileNotFoundTemplate;
- }
- $visitingUser = $this->userSession->getUser();
-
- if (!$this->profileManager->isProfileEnabled($targetUser)) {
- return $profileNotFoundTemplate;
- }
-
- // Run user enumeration checks only if viewing another user's profile
- if ($targetUser !== $visitingUser) {
- if (!$this->shareManager->currentUserCanEnumerateTargetUser($visitingUser, $targetUser)) {
- return $profileNotFoundTemplate;
- }
- }
-
- if ($visitingUser !== null) {
- $userStatuses = $this->userStatusManager->getUserStatuses([$targetUserId]);
- $status = $userStatuses[$targetUserId] ?? null;
- if ($status !== null) {
- $this->initialStateService->provideInitialState('status', [
- 'icon' => $status->getIcon(),
- 'message' => $status->getMessage(),
- ]);
- }
- }
-
- $this->initialStateService->provideInitialState(
- 'profileParameters',
- $this->profileManager->getProfileFields($targetUser, $visitingUser),
- );
-
- if ($targetUser === $visitingUser) {
- $this->navigationManager->setActiveEntry('profile');
- }
-
- $this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($targetUserId));
-
- \OCP\Util::addScript('core', 'profile');
-
- return new TemplateResponse(
- 'core',
- 'profile',
- [],
- $this->userSession->isLoggedIn() ? TemplateResponse::RENDER_AS_USER : TemplateResponse::RENDER_AS_PUBLIC,
- );
- }
-}
diff --git a/core/Controller/RecommendedAppsController.php b/core/Controller/RecommendedAppsController.php
index 5d4749e1e83..ba35bc8705e 100644
--- a/core/Controller/RecommendedAppsController.php
+++ b/core/Controller/RecommendedAppsController.php
@@ -3,31 +3,14 @@
declare(strict_types=1);
/**
- * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\StandaloneTemplateResponse;
@@ -46,9 +29,9 @@ class RecommendedAppsController extends Controller {
}
/**
- * @NoCSRFRequired
* @return Response
*/
+ #[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/core/apps/recommended')]
public function index(): Response {
$defaultPageUrl = $this->urlGenerator->linkToDefaultPageUrl();
diff --git a/core/Controller/ReferenceApiController.php b/core/Controller/ReferenceApiController.php
index 854c15cb985..d4fb753f404 100644
--- a/core/Controller/ReferenceApiController.php
+++ b/core/Controller/ReferenceApiController.php
@@ -2,33 +2,20 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2022 Julius Härtl <jus@bitgrid.net>
- *
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
-use OCA\Core\ResponseDefinitions;
+use OC\Core\ResponseDefinitions;
use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\AnonRateLimit;
use OCP\AppFramework\Http\Attribute\ApiRoute;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCSController;
use OCP\Collaboration\Reference\IDiscoverableReferenceProvider;
use OCP\Collaboration\Reference\IReferenceManager;
use OCP\Collaboration\Reference\Reference;
@@ -38,7 +25,9 @@ use OCP\IRequest;
* @psalm-import-type CoreReference from ResponseDefinitions
* @psalm-import-type CoreReferenceProvider from ResponseDefinitions
*/
-class ReferenceApiController extends \OCP\AppFramework\OCSController {
+class ReferenceApiController extends OCSController {
+ private const LIMIT_MAX = 15;
+
public function __construct(
string $appName,
IRequest $request,
@@ -49,8 +38,6 @@ class ReferenceApiController extends \OCP\AppFramework\OCSController {
}
/**
- * @NoAdminRequired
- *
* Extract references from a text
*
* @param string $text Text to extract from
@@ -60,6 +47,7 @@ class ReferenceApiController extends \OCP\AppFramework\OCSController {
*
* 200: References returned
*/
+ #[NoAdminRequired]
#[ApiRoute(verb: 'POST', url: '/extract', root: '/references')]
public function extract(string $text, bool $resolve = false, int $limit = 1): DataResponse {
$references = $this->referenceManager->extractReferences($text);
@@ -80,8 +68,38 @@ class ReferenceApiController extends \OCP\AppFramework\OCSController {
}
/**
- * @NoAdminRequired
+ * Extract references from a text
+ *
+ * @param string $text Text to extract from
+ * @param string $sharingToken Token of the public share
+ * @param bool $resolve Resolve the references
+ * @param int $limit Maximum amount of references to extract, limited to 15
+ * @return DataResponse<Http::STATUS_OK, array{references: array<string, CoreReference|null>}, array{}>
*
+ * 200: References returned
+ */
+ #[ApiRoute(verb: 'POST', url: '/extractPublic', root: '/references')]
+ #[PublicPage]
+ #[AnonRateLimit(limit: 10, period: 120)]
+ public function extractPublic(string $text, string $sharingToken, bool $resolve = false, int $limit = 1): DataResponse {
+ $references = $this->referenceManager->extractReferences($text);
+
+ $result = [];
+ $index = 0;
+ foreach ($references as $reference) {
+ if ($index++ >= min($limit, self::LIMIT_MAX)) {
+ break;
+ }
+
+ $result[$reference] = $resolve ? $this->referenceManager->resolveReference($reference, true, $sharingToken)?->jsonSerialize() : null;
+ }
+
+ return new DataResponse([
+ 'references' => $result
+ ]);
+ }
+
+ /**
* Resolve a reference
*
* @param string $reference Reference to resolve
@@ -89,6 +107,7 @@ class ReferenceApiController extends \OCP\AppFramework\OCSController {
*
* 200: Reference returned
*/
+ #[NoAdminRequired]
#[ApiRoute(verb: 'GET', url: '/resolve', root: '/references')]
public function resolveOne(string $reference): DataResponse {
/** @var ?CoreReference $resolvedReference */
@@ -100,16 +119,36 @@ class ReferenceApiController extends \OCP\AppFramework\OCSController {
}
/**
- * @NoAdminRequired
+ * Resolve from a public page
*
+ * @param string $reference Reference to resolve
+ * @param string $sharingToken Token of the public share
+ * @return DataResponse<Http::STATUS_OK, array{references: array<string, ?CoreReference>}, array{}>
+ *
+ * 200: Reference returned
+ */
+ #[ApiRoute(verb: 'GET', url: '/resolvePublic', root: '/references')]
+ #[PublicPage]
+ #[AnonRateLimit(limit: 25, period: 120)]
+ public function resolveOnePublic(string $reference, string $sharingToken): DataResponse {
+ /** @var ?CoreReference $resolvedReference */
+ $resolvedReference = $this->referenceManager->resolveReference(trim($reference), true, trim($sharingToken))?->jsonSerialize();
+
+ $response = new DataResponse(['references' => [$reference => $resolvedReference]]);
+ $response->cacheFor(3600, false, true);
+ return $response;
+ }
+
+ /**
* Resolve multiple references
*
- * @param string[] $references References to resolve
+ * @param list<string> $references References to resolve
* @param int $limit Maximum amount of references to resolve
* @return DataResponse<Http::STATUS_OK, array{references: array<string, CoreReference|null>}, array{}>
*
* 200: References returned
*/
+ #[NoAdminRequired]
#[ApiRoute(verb: 'POST', url: '/resolve', root: '/references')]
public function resolve(array $references, int $limit = 1): DataResponse {
$result = [];
@@ -128,26 +167,52 @@ class ReferenceApiController extends \OCP\AppFramework\OCSController {
}
/**
- * @NoAdminRequired
+ * Resolve multiple references from a public page
+ *
+ * @param list<string> $references References to resolve
+ * @param string $sharingToken Token of the public share
+ * @param int $limit Maximum amount of references to resolve, limited to 15
+ * @return DataResponse<Http::STATUS_OK, array{references: array<string, CoreReference|null>}, array{}>
*
+ * 200: References returned
+ */
+ #[ApiRoute(verb: 'POST', url: '/resolvePublic', root: '/references')]
+ #[PublicPage]
+ #[AnonRateLimit(limit: 10, period: 120)]
+ public function resolvePublic(array $references, string $sharingToken, int $limit = 1): DataResponse {
+ $result = [];
+ $index = 0;
+ foreach ($references as $reference) {
+ if ($index++ >= min($limit, self::LIMIT_MAX)) {
+ break;
+ }
+
+ $result[$reference] = $this->referenceManager->resolveReference($reference, true, $sharingToken)?->jsonSerialize();
+ }
+
+ return new DataResponse([
+ 'references' => $result
+ ]);
+ }
+
+ /**
* Get the providers
*
- * @return DataResponse<Http::STATUS_OK, CoreReferenceProvider[], array{}>
+ * @return DataResponse<Http::STATUS_OK, list<CoreReferenceProvider>, array{}>
*
* 200: Providers returned
*/
+ #[NoAdminRequired]
#[ApiRoute(verb: 'GET', url: '/providers', root: '/references')]
public function getProvidersInfo(): DataResponse {
$providers = $this->referenceManager->getDiscoverableProviders();
- $jsonProviders = array_map(static function (IDiscoverableReferenceProvider $provider) {
+ $jsonProviders = array_values(array_map(static function (IDiscoverableReferenceProvider $provider) {
return $provider->jsonSerialize();
- }, $providers);
+ }, $providers));
return new DataResponse($jsonProviders);
}
/**
- * @NoAdminRequired
- *
* Touch a provider
*
* @param string $providerId ID of the provider
@@ -156,6 +221,7 @@ class ReferenceApiController extends \OCP\AppFramework\OCSController {
*
* 200: Provider touched
*/
+ #[NoAdminRequired]
#[ApiRoute(verb: 'PUT', url: '/provider/{providerId}', root: '/references')]
public function touchProvider(string $providerId, ?int $timestamp = null): DataResponse {
if ($this->userId !== null) {
diff --git a/core/Controller/ReferenceController.php b/core/Controller/ReferenceController.php
index 8874978037f..6ed15e2d2f1 100644
--- a/core/Controller/ReferenceController.php
+++ b/core/Controller/ReferenceController.php
@@ -2,25 +2,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2022 Julius Härtl <jus@bitgrid.net>
- *
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
@@ -28,6 +11,9 @@ namespace OC\Core\Controller;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\DataDownloadResponse;
use OCP\AppFramework\Http\DataResponse;
use OCP\Collaboration\Reference\IReferenceManager;
@@ -47,9 +33,6 @@ class ReferenceController extends Controller {
}
/**
- * @PublicPage
- * @NoCSRFRequired
- *
* Get a preview for a reference
*
* @param string $referenceId the reference cache key
@@ -58,7 +41,10 @@ class ReferenceController extends Controller {
* 200: Preview returned
* 404: Reference not found
*/
+ #[PublicPage]
+ #[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/core/references/preview/{referenceId}')]
+ #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function preview(string $referenceId): DataDownloadResponse|DataResponse {
$reference = $this->referenceManager->getReferenceByCacheKey($referenceId);
diff --git a/core/Controller/SearchController.php b/core/Controller/SearchController.php
deleted file mode 100644
index ccea067ae2c..00000000000
--- a/core/Controller/SearchController.php
+++ /dev/null
@@ -1,65 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-namespace OC\Core\Controller;
-
-use OCP\AppFramework\Controller;
-use OCP\AppFramework\Http\Attribute\FrontpageRoute;
-use OCP\AppFramework\Http\JSONResponse;
-use OCP\IRequest;
-use OCP\ISearch;
-use OCP\Search\Result;
-use Psr\Log\LoggerInterface;
-
-class SearchController extends Controller {
- public function __construct(
- string $appName,
- IRequest $request,
- private ISearch $searcher,
- private LoggerInterface $logger,
- ) {
- parent::__construct($appName, $request);
- }
-
- /**
- * @NoAdminRequired
- */
- #[FrontpageRoute(verb: 'GET', url: '/core/search')]
- public function search(string $query, array $inApps = [], int $page = 1, int $size = 30): JSONResponse {
- $results = $this->searcher->searchPaged($query, $inApps, $page, $size);
-
- $results = array_filter($results, function (Result $result) {
- if (json_encode($result, JSON_HEX_TAG) === false) {
- $this->logger->warning("Skipping search result due to invalid encoding: {type: " . $result->type . ", id: " . $result->id . "}");
- return false;
- } else {
- return true;
- }
- });
-
- return new JSONResponse($results);
- }
-}
diff --git a/core/Controller/SetupController.php b/core/Controller/SetupController.php
index 0f1fa1c495e..f89506680ad 100644
--- a/core/Controller/SetupController.php
+++ b/core/Controller/SetupController.php
@@ -1,37 +1,18 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Bart Visscher <bartv@thisnet.nl>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Damjan Georgievski <gdamjan@gmail.com>
- * @author ideaship <ideaship@users.noreply.github.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Robin McCorkell <robin@mccorkell.me.uk>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Controller;
+use OC\IntegrityCheck\Checker;
use OC\Setup;
+use OCP\IInitialStateService;
+use OCP\IURLGenerator;
+use OCP\Server;
+use OCP\Template\ITemplateManager;
use OCP\Util;
use Psr\Log\LoggerInterface;
@@ -41,8 +22,11 @@ class SetupController {
public function __construct(
protected Setup $setupHelper,
protected LoggerInterface $logger,
+ protected ITemplateManager $templateManager,
+ protected IInitialStateService $initialStateService,
+ protected IURLGenerator $urlGenerator,
) {
- $this->autoConfigFile = \OC::$configDir.'autoconfig.php';
+ $this->autoConfigFile = \OC::$configDir . 'autoconfig.php';
}
public function run(array $post): void {
@@ -81,10 +65,10 @@ class SetupController {
}
private function displaySetupForbidden(): void {
- \OC_Template::printGuestPage('', 'installation_forbidden');
+ $this->templateManager->printGuestPage('', 'installation_forbidden');
}
- public function display($post): void {
+ public function display(array $post): void {
$defaults = [
'adminlogin' => '',
'adminpass' => '',
@@ -94,6 +78,8 @@ class SetupController {
'dbtablespace' => '',
'dbhost' => 'localhost',
'dbtype' => '',
+ 'hasAutoconfig' => false,
+ 'serverRoot' => \OC::$SERVERROOT,
];
$parameters = array_merge($defaults, $post);
@@ -102,30 +88,43 @@ class SetupController {
// include common nextcloud webpack bundle
Util::addScript('core', 'common');
Util::addScript('core', 'main');
+ Util::addScript('core', 'install');
Util::addTranslations('core');
- \OC_Template::printGuestPage('', 'installation', $parameters);
+ $this->initialStateService->provideInitialState('core', 'config', $parameters);
+ $this->initialStateService->provideInitialState('core', 'data', false);
+ $this->initialStateService->provideInitialState('core', 'links', [
+ 'adminInstall' => $this->urlGenerator->linkToDocs('admin-install'),
+ 'adminSourceInstall' => $this->urlGenerator->linkToDocs('admin-source_install'),
+ 'adminDBConfiguration' => $this->urlGenerator->linkToDocs('admin-db-configuration'),
+ ]);
+
+ $this->templateManager->printGuestPage('', 'installation');
}
private function finishSetup(): void {
if (file_exists($this->autoConfigFile)) {
unlink($this->autoConfigFile);
}
- \OC::$server->getIntegrityCodeChecker()->runInstanceVerification();
+ Server::get(Checker::class)->runInstanceVerification();
if ($this->setupHelper->shouldRemoveCanInstallFile()) {
- \OC_Template::printGuestPage('', 'installation_incomplete');
+ $this->templateManager->printGuestPage('', 'installation_incomplete');
}
- header('Location: ' . \OC::$server->getURLGenerator()->getAbsoluteURL('index.php/core/apps/recommended'));
+ header('Location: ' . Server::get(IURLGenerator::class)->getAbsoluteURL('index.php/core/apps/recommended'));
exit();
}
+ /**
+ * @psalm-taint-escape file we trust file path given in POST for setup
+ */
public function loadAutoConfig(array $post): array {
if (file_exists($this->autoConfigFile)) {
$this->logger->info('Autoconfig file found, setting up Nextcloud…');
$AUTOCONFIG = [];
include $this->autoConfigFile;
+ $post['hasAutoconfig'] = count($AUTOCONFIG) > 0;
$post = array_merge($post, $AUTOCONFIG);
}
@@ -136,8 +135,6 @@ class SetupController {
if ($dbIsSet and $directoryIsSet and $adminAccountIsSet) {
$post['install'] = 'true';
}
- $post['dbIsSet'] = $dbIsSet;
- $post['directoryIsSet'] = $directoryIsSet;
return $post;
}
diff --git a/core/Controller/TaskProcessingApiController.php b/core/Controller/TaskProcessingApiController.php
new file mode 100644
index 00000000000..90a0e9ba14a
--- /dev/null
+++ b/core/Controller/TaskProcessingApiController.php
@@ -0,0 +1,657 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+
+namespace OC\Core\Controller;
+
+use OC\Core\ResponseDefinitions;
+use OC\Files\SimpleFS\SimpleFile;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\AnonRateLimit;
+use OCP\AppFramework\Http\Attribute\ApiRoute;
+use OCP\AppFramework\Http\Attribute\ExAppRequired;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\PublicPage;
+use OCP\AppFramework\Http\Attribute\UserRateLimit;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\Http\StreamResponse;
+use OCP\AppFramework\OCSController;
+use OCP\Files\File;
+use OCP\Files\IAppData;
+use OCP\Files\IMimeTypeDetector;
+use OCP\Files\IRootFolder;
+use OCP\Files\NotPermittedException;
+use OCP\IL10N;
+use OCP\IRequest;
+use OCP\Lock\LockedException;
+use OCP\TaskProcessing\EShapeType;
+use OCP\TaskProcessing\Exception\Exception;
+use OCP\TaskProcessing\Exception\NotFoundException;
+use OCP\TaskProcessing\Exception\PreConditionNotMetException;
+use OCP\TaskProcessing\Exception\UnauthorizedException;
+use OCP\TaskProcessing\Exception\ValidationException;
+use OCP\TaskProcessing\IManager;
+use OCP\TaskProcessing\ShapeEnumValue;
+use OCP\TaskProcessing\Task;
+use RuntimeException;
+use stdClass;
+
+/**
+ * @psalm-import-type CoreTaskProcessingTask from ResponseDefinitions
+ * @psalm-import-type CoreTaskProcessingTaskType from ResponseDefinitions
+ */
+class TaskProcessingApiController extends OCSController {
+ public function __construct(
+ string $appName,
+ IRequest $request,
+ private IManager $taskProcessingManager,
+ private IL10N $l,
+ private ?string $userId,
+ private IRootFolder $rootFolder,
+ private IAppData $appData,
+ private IMimeTypeDetector $mimeTypeDetector,
+ ) {
+ parent::__construct($appName, $request);
+ }
+
+ /**
+ * Returns all available TaskProcessing task types
+ *
+ * @return DataResponse<Http::STATUS_OK, array{types: array<string, CoreTaskProcessingTaskType>}, array{}>
+ *
+ * 200: Task types returned
+ */
+ #[PublicPage]
+ #[ApiRoute(verb: 'GET', url: '/tasktypes', root: '/taskprocessing')]
+ public function taskTypes(): DataResponse {
+ /** @var array<string, CoreTaskProcessingTaskType> $taskTypes */
+ $taskTypes = array_map(function (array $tt) {
+ $tt['inputShape'] = array_map(function ($descriptor) {
+ return $descriptor->jsonSerialize();
+ }, $tt['inputShape']);
+ if (empty($tt['inputShape'])) {
+ $tt['inputShape'] = new stdClass;
+ }
+
+ $tt['outputShape'] = array_map(function ($descriptor) {
+ return $descriptor->jsonSerialize();
+ }, $tt['outputShape']);
+ if (empty($tt['outputShape'])) {
+ $tt['outputShape'] = new stdClass;
+ }
+
+ $tt['optionalInputShape'] = array_map(function ($descriptor) {
+ return $descriptor->jsonSerialize();
+ }, $tt['optionalInputShape']);
+ if (empty($tt['optionalInputShape'])) {
+ $tt['optionalInputShape'] = new stdClass;
+ }
+
+ $tt['optionalOutputShape'] = array_map(function ($descriptor) {
+ return $descriptor->jsonSerialize();
+ }, $tt['optionalOutputShape']);
+ if (empty($tt['optionalOutputShape'])) {
+ $tt['optionalOutputShape'] = new stdClass;
+ }
+
+ $tt['inputShapeEnumValues'] = array_map(function (array $enumValues) {
+ return array_map(fn (ShapeEnumValue $enumValue) => $enumValue->jsonSerialize(), $enumValues);
+ }, $tt['inputShapeEnumValues']);
+ if (empty($tt['inputShapeEnumValues'])) {
+ $tt['inputShapeEnumValues'] = new stdClass;
+ }
+
+ $tt['optionalInputShapeEnumValues'] = array_map(function (array $enumValues) {
+ return array_map(fn (ShapeEnumValue $enumValue) => $enumValue->jsonSerialize(), $enumValues);
+ }, $tt['optionalInputShapeEnumValues']);
+ if (empty($tt['optionalInputShapeEnumValues'])) {
+ $tt['optionalInputShapeEnumValues'] = new stdClass;
+ }
+
+ $tt['outputShapeEnumValues'] = array_map(function (array $enumValues) {
+ return array_map(fn (ShapeEnumValue $enumValue) => $enumValue->jsonSerialize(), $enumValues);
+ }, $tt['outputShapeEnumValues']);
+ if (empty($tt['outputShapeEnumValues'])) {
+ $tt['outputShapeEnumValues'] = new stdClass;
+ }
+
+ $tt['optionalOutputShapeEnumValues'] = array_map(function (array $enumValues) {
+ return array_map(fn (ShapeEnumValue $enumValue) => $enumValue->jsonSerialize(), $enumValues);
+ }, $tt['optionalOutputShapeEnumValues']);
+ if (empty($tt['optionalOutputShapeEnumValues'])) {
+ $tt['optionalOutputShapeEnumValues'] = new stdClass;
+ }
+
+ if (empty($tt['inputShapeDefaults'])) {
+ $tt['inputShapeDefaults'] = new stdClass;
+ }
+ if (empty($tt['optionalInputShapeDefaults'])) {
+ $tt['optionalInputShapeDefaults'] = new stdClass;
+ }
+ return $tt;
+ }, $this->taskProcessingManager->getAvailableTaskTypes());
+ return new DataResponse([
+ 'types' => $taskTypes,
+ ]);
+ }
+
+ /**
+ * Schedules a task
+ *
+ * @param array<string, mixed> $input Task's input parameters
+ * @param string $type Type of the task
+ * @param string $appId ID of the app that will execute the task
+ * @param string $customId An arbitrary identifier for the task
+ * @param string|null $webhookUri URI to be requested when the task finishes
+ * @param string|null $webhookMethod Method used for the webhook request (HTTP:GET, HTTP:POST, HTTP:PUT, HTTP:DELETE or AppAPI:APP_ID:GET, AppAPI:APP_ID:POST...)
+ * @return DataResponse<Http::STATUS_OK, array{task: CoreTaskProcessingTask}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_BAD_REQUEST|Http::STATUS_PRECONDITION_FAILED|Http::STATUS_UNAUTHORIZED, array{message: string}, array{}>
+ *
+ * 200: Task scheduled successfully
+ * 400: Scheduling task is not possible
+ * 412: Scheduling task is not possible
+ * 401: Cannot schedule task because it references files in its input that the user doesn't have access to
+ */
+ #[PublicPage]
+ #[UserRateLimit(limit: 20, period: 120)]
+ #[AnonRateLimit(limit: 5, period: 120)]
+ #[ApiRoute(verb: 'POST', url: '/schedule', root: '/taskprocessing')]
+ public function schedule(
+ array $input, string $type, string $appId, string $customId = '',
+ ?string $webhookUri = null, ?string $webhookMethod = null,
+ ): DataResponse {
+ $task = new Task($type, $input, $appId, $this->userId, $customId);
+ $task->setWebhookUri($webhookUri);
+ $task->setWebhookMethod($webhookMethod);
+ try {
+ $this->taskProcessingManager->scheduleTask($task);
+
+ /** @var CoreTaskProcessingTask $json */
+ $json = $task->jsonSerialize();
+
+ return new DataResponse([
+ 'task' => $json,
+ ]);
+ } catch (PreConditionNotMetException) {
+ return new DataResponse(['message' => $this->l->t('The given provider is not available')], Http::STATUS_PRECONDITION_FAILED);
+ } catch (ValidationException $e) {
+ return new DataResponse(['message' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
+ } catch (UnauthorizedException) {
+ return new DataResponse(['message' => 'User does not have access to the files mentioned in the task input'], Http::STATUS_UNAUTHORIZED);
+ } catch (Exception) {
+ return new DataResponse(['message' => 'Internal server error'], Http::STATUS_INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ /**
+ * Gets a task including status and result
+ *
+ * Tasks are removed 1 week after receiving their last update
+ *
+ * @param int $id The id of the task
+ *
+ * @return DataResponse<Http::STATUS_OK, array{task: CoreTaskProcessingTask}, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
+ *
+ * 200: Task returned
+ * 404: Task not found
+ */
+ #[PublicPage]
+ #[ApiRoute(verb: 'GET', url: '/task/{id}', root: '/taskprocessing')]
+ public function getTask(int $id): DataResponse {
+ try {
+ $task = $this->taskProcessingManager->getUserTask($id, $this->userId);
+
+ /** @var CoreTaskProcessingTask $json */
+ $json = $task->jsonSerialize();
+
+ return new DataResponse([
+ 'task' => $json,
+ ]);
+ } catch (NotFoundException) {
+ return new DataResponse(['message' => $this->l->t('Task not found')], Http::STATUS_NOT_FOUND);
+ } catch (RuntimeException) {
+ return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ /**
+ * Deletes a task
+ *
+ * @param int $id The id of the task
+ *
+ * @return DataResponse<Http::STATUS_OK, null, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
+ *
+ * 200: Task deleted
+ */
+ #[NoAdminRequired]
+ #[ApiRoute(verb: 'DELETE', url: '/task/{id}', root: '/taskprocessing')]
+ public function deleteTask(int $id): DataResponse {
+ try {
+ $task = $this->taskProcessingManager->getUserTask($id, $this->userId);
+
+ $this->taskProcessingManager->deleteTask($task);
+
+ return new DataResponse(null);
+ } catch (NotFoundException) {
+ return new DataResponse(null);
+ } catch (Exception) {
+ return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
+ }
+ }
+
+
+ /**
+ * Returns tasks for the current user filtered by the appId and optional customId
+ *
+ * @param string $appId ID of the app
+ * @param string|null $customId An arbitrary identifier for the task
+ * @return DataResponse<Http::STATUS_OK, array{tasks: list<CoreTaskProcessingTask>}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
+ *
+ * 200: Tasks returned
+ */
+ #[NoAdminRequired]
+ #[ApiRoute(verb: 'GET', url: '/tasks/app/{appId}', root: '/taskprocessing')]
+ public function listTasksByApp(string $appId, ?string $customId = null): DataResponse {
+ try {
+ $tasks = $this->taskProcessingManager->getUserTasksByApp($this->userId, $appId, $customId);
+ $json = array_map(static function (Task $task) {
+ return $task->jsonSerialize();
+ }, $tasks);
+
+ return new DataResponse([
+ 'tasks' => $json,
+ ]);
+ } catch (Exception) {
+ return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ /**
+ * Returns tasks for the current user filtered by the optional taskType and optional customId
+ *
+ * @param string|null $taskType The task type to filter by
+ * @param string|null $customId An arbitrary identifier for the task
+ * @return DataResponse<Http::STATUS_OK, array{tasks: list<CoreTaskProcessingTask>}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
+ *
+ * 200: Tasks returned
+ */
+ #[NoAdminRequired]
+ #[ApiRoute(verb: 'GET', url: '/tasks', root: '/taskprocessing')]
+ public function listTasks(?string $taskType, ?string $customId = null): DataResponse {
+ try {
+ $tasks = $this->taskProcessingManager->getUserTasks($this->userId, $taskType, $customId);
+ $json = array_map(static function (Task $task) {
+ return $task->jsonSerialize();
+ }, $tasks);
+
+ return new DataResponse([
+ 'tasks' => $json,
+ ]);
+ } catch (Exception) {
+ return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ /**
+ * Returns the contents of a file referenced in a task
+ *
+ * @param int $taskId The id of the task
+ * @param int $fileId The file id of the file to retrieve
+ * @return StreamResponse<Http::STATUS_OK, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
+ *
+ * 200: File content returned
+ * 404: Task or file not found
+ */
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
+ #[ApiRoute(verb: 'GET', url: '/tasks/{taskId}/file/{fileId}', root: '/taskprocessing')]
+ public function getFileContents(int $taskId, int $fileId): StreamResponse|DataResponse {
+ try {
+ $task = $this->taskProcessingManager->getUserTask($taskId, $this->userId);
+ return $this->getFileContentsInternal($task, $fileId);
+ } catch (NotFoundException) {
+ return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND);
+ } catch (LockedException) {
+ return new DataResponse(['message' => $this->l->t('Node is locked')], Http::STATUS_INTERNAL_SERVER_ERROR);
+ } catch (Exception) {
+ return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ /**
+ * Returns the contents of a file referenced in a task(ExApp route version)
+ *
+ * @param int $taskId The id of the task
+ * @param int $fileId The file id of the file to retrieve
+ * @return StreamResponse<Http::STATUS_OK, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
+ *
+ * 200: File content returned
+ * 404: Task or file not found
+ */
+ #[ExAppRequired]
+ #[ApiRoute(verb: 'GET', url: '/tasks_provider/{taskId}/file/{fileId}', root: '/taskprocessing')]
+ public function getFileContentsExApp(int $taskId, int $fileId): StreamResponse|DataResponse {
+ try {
+ $task = $this->taskProcessingManager->getTask($taskId);
+ return $this->getFileContentsInternal($task, $fileId);
+ } catch (NotFoundException) {
+ return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND);
+ } catch (LockedException) {
+ return new DataResponse(['message' => $this->l->t('Node is locked')], Http::STATUS_INTERNAL_SERVER_ERROR);
+ } catch (Exception) {
+ return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ /**
+ * Upload a file so it can be referenced in a task result (ExApp route version)
+ *
+ * Use field 'file' for the file upload
+ *
+ * @param int $taskId The id of the task
+ * @return DataResponse<Http::STATUS_CREATED, array{fileId: int}, array{}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
+ *
+ * 201: File created
+ * 400: File upload failed or no file was uploaded
+ * 404: Task not found
+ */
+ #[ExAppRequired]
+ #[ApiRoute(verb: 'POST', url: '/tasks_provider/{taskId}/file', root: '/taskprocessing')]
+ public function setFileContentsExApp(int $taskId): DataResponse {
+ try {
+ $task = $this->taskProcessingManager->getTask($taskId);
+ $file = $this->request->getUploadedFile('file');
+ if (!isset($file['tmp_name'])) {
+ return new DataResponse(['message' => $this->l->t('Bad request')], Http::STATUS_BAD_REQUEST);
+ }
+ $handle = fopen($file['tmp_name'], 'r');
+ if (!$handle) {
+ return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
+ }
+ $fileId = $this->setFileContentsInternal($handle);
+ return new DataResponse(['fileId' => $fileId], Http::STATUS_CREATED);
+ } catch (NotFoundException) {
+ return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND);
+ } catch (Exception) {
+ return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ /**
+ * @throws NotPermittedException
+ * @throws NotFoundException
+ * @throws LockedException
+ *
+ * @return StreamResponse<Http::STATUS_OK, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
+ */
+ private function getFileContentsInternal(Task $task, int $fileId): StreamResponse|DataResponse {
+ $ids = $this->extractFileIdsFromTask($task);
+ if (!in_array($fileId, $ids)) {
+ return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND);
+ }
+ if ($task->getUserId() !== null) {
+ \OC_Util::setupFS($task->getUserId());
+ }
+ $node = $this->rootFolder->getFirstNodeById($fileId);
+ if ($node === null) {
+ $node = $this->rootFolder->getFirstNodeByIdInPath($fileId, '/' . $this->rootFolder->getAppDataDirectoryName() . '/');
+ if (!$node instanceof File) {
+ throw new NotFoundException('Node is not a file');
+ }
+ } elseif (!$node instanceof File) {
+ throw new NotFoundException('Node is not a file');
+ }
+
+ $contentType = $node->getMimeType();
+ if (function_exists('mime_content_type')) {
+ $mimeType = mime_content_type($node->fopen('rb'));
+ if ($mimeType !== false) {
+ $mimeType = $this->mimeTypeDetector->getSecureMimeType($mimeType);
+ if ($mimeType !== 'application/octet-stream') {
+ $contentType = $mimeType;
+ }
+ }
+ }
+
+ $response = new StreamResponse($node->fopen('rb'));
+ $response->addHeader(
+ 'Content-Disposition',
+ 'attachment; filename="' . rawurldecode($node->getName()) . '"'
+ );
+ $response->addHeader('Content-Type', $contentType);
+ return $response;
+ }
+
+ /**
+ * @param Task $task
+ * @return list<int>
+ * @throws NotFoundException
+ */
+ private function extractFileIdsFromTask(Task $task): array {
+ $ids = [];
+ $taskTypes = $this->taskProcessingManager->getAvailableTaskTypes();
+ if (!isset($taskTypes[$task->getTaskTypeId()])) {
+ throw new NotFoundException('Could not find task type');
+ }
+ $taskType = $taskTypes[$task->getTaskTypeId()];
+ foreach ($taskType['inputShape'] + $taskType['optionalInputShape'] as $key => $descriptor) {
+ if (in_array(EShapeType::getScalarType($descriptor->getShapeType()), [EShapeType::File, EShapeType::Image, EShapeType::Audio, EShapeType::Video], true)) {
+ /** @var int|list<int> $inputSlot */
+ $inputSlot = $task->getInput()[$key];
+ if (is_array($inputSlot)) {
+ $ids = array_merge($inputSlot, $ids);
+ } else {
+ $ids[] = $inputSlot;
+ }
+ }
+ }
+ if ($task->getOutput() !== null) {
+ foreach ($taskType['outputShape'] + $taskType['optionalOutputShape'] as $key => $descriptor) {
+ if (in_array(EShapeType::getScalarType($descriptor->getShapeType()), [EShapeType::File, EShapeType::Image, EShapeType::Audio, EShapeType::Video], true)) {
+ /** @var int|list<int> $outputSlot */
+ $outputSlot = $task->getOutput()[$key];
+ if (is_array($outputSlot)) {
+ $ids = array_merge($outputSlot, $ids);
+ } else {
+ $ids[] = $outputSlot;
+ }
+ }
+ }
+ }
+ return $ids;
+ }
+
+ /**
+ * Sets the task progress
+ *
+ * @param int $taskId The id of the task
+ * @param float $progress The progress
+ * @return DataResponse<Http::STATUS_OK, array{task: CoreTaskProcessingTask}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
+ *
+ * 200: Progress updated successfully
+ * 404: Task not found
+ */
+ #[ExAppRequired]
+ #[ApiRoute(verb: 'POST', url: '/tasks_provider/{taskId}/progress', root: '/taskprocessing')]
+ public function setProgress(int $taskId, float $progress): DataResponse {
+ try {
+ $this->taskProcessingManager->setTaskProgress($taskId, $progress);
+ $task = $this->taskProcessingManager->getTask($taskId);
+
+ /** @var CoreTaskProcessingTask $json */
+ $json = $task->jsonSerialize();
+
+ return new DataResponse([
+ 'task' => $json,
+ ]);
+ } catch (NotFoundException) {
+ return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND);
+ } catch (Exception) {
+ return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ /**
+ * Sets the task result
+ *
+ * @param int $taskId The id of the task
+ * @param array<string,mixed>|null $output The resulting task output, files are represented by their IDs
+ * @param string|null $errorMessage An error message if the task failed
+ * @return DataResponse<Http::STATUS_OK, array{task: CoreTaskProcessingTask}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
+ *
+ * 200: Result updated successfully
+ * 404: Task not found
+ */
+ #[ExAppRequired]
+ #[ApiRoute(verb: 'POST', url: '/tasks_provider/{taskId}/result', root: '/taskprocessing')]
+ public function setResult(int $taskId, ?array $output = null, ?string $errorMessage = null): DataResponse {
+ try {
+ // set result
+ $this->taskProcessingManager->setTaskResult($taskId, $errorMessage, $output, true);
+ $task = $this->taskProcessingManager->getTask($taskId);
+
+ /** @var CoreTaskProcessingTask $json */
+ $json = $task->jsonSerialize();
+
+ return new DataResponse([
+ 'task' => $json,
+ ]);
+ } catch (NotFoundException) {
+ return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND);
+ } catch (Exception) {
+ return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ /**
+ * Cancels a task
+ *
+ * @param int $taskId The id of the task
+ * @return DataResponse<Http::STATUS_OK, array{task: CoreTaskProcessingTask}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
+ *
+ * 200: Task canceled successfully
+ * 404: Task not found
+ */
+ #[NoAdminRequired]
+ #[ApiRoute(verb: 'POST', url: '/tasks/{taskId}/cancel', root: '/taskprocessing')]
+ public function cancelTask(int $taskId): DataResponse {
+ try {
+ // Check if the current user can access the task
+ $this->taskProcessingManager->getUserTask($taskId, $this->userId);
+ // set result
+ $this->taskProcessingManager->cancelTask($taskId);
+ $task = $this->taskProcessingManager->getUserTask($taskId, $this->userId);
+
+ /** @var CoreTaskProcessingTask $json */
+ $json = $task->jsonSerialize();
+
+ return new DataResponse([
+ 'task' => $json,
+ ]);
+ } catch (NotFoundException) {
+ return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND);
+ } catch (Exception) {
+ return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ /**
+ * Returns the next scheduled task for the taskTypeId
+ *
+ * @param list<string> $providerIds The ids of the providers
+ * @param list<string> $taskTypeIds The ids of the task types
+ * @return DataResponse<Http::STATUS_OK, array{task: CoreTaskProcessingTask, provider: array{name: string}}, array{}>|DataResponse<Http::STATUS_NO_CONTENT, null, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
+ *
+ * 200: Task returned
+ * 204: No task found
+ */
+ #[ExAppRequired]
+ #[ApiRoute(verb: 'GET', url: '/tasks_provider/next', root: '/taskprocessing')]
+ public function getNextScheduledTask(array $providerIds, array $taskTypeIds): DataResponse {
+ try {
+ $providerIdsBasedOnTaskTypesWithNull = array_unique(array_map(function ($taskTypeId) {
+ try {
+ return $this->taskProcessingManager->getPreferredProvider($taskTypeId)->getId();
+ } catch (Exception) {
+ return null;
+ }
+ }, $taskTypeIds));
+
+ $providerIdsBasedOnTaskTypes = array_filter($providerIdsBasedOnTaskTypesWithNull, fn ($providerId) => $providerId !== null);
+
+ // restrict $providerIds to providers that are configured as preferred for the passed task types
+ $possibleProviderIds = array_values(array_intersect($providerIdsBasedOnTaskTypes, $providerIds));
+
+ // restrict $taskTypeIds to task types that can actually be run by one of the now restricted providers
+ $possibleTaskTypeIds = array_values(array_filter($taskTypeIds, function ($taskTypeId) use ($possibleProviderIds) {
+ try {
+ $providerForTaskType = $this->taskProcessingManager->getPreferredProvider($taskTypeId)->getId();
+ } catch (Exception) {
+ // no provider found for task type
+ return false;
+ }
+ return in_array($providerForTaskType, $possibleProviderIds, true);
+ }));
+
+ if (count($possibleProviderIds) === 0 || count($possibleTaskTypeIds) === 0) {
+ throw new NotFoundException();
+ }
+
+ $taskIdsToIgnore = [];
+ while (true) {
+ // Until we find a task whose task type is set to be provided by the providers requested with this request
+ // Or no scheduled task is found anymore (given the taskIds to ignore)
+ $task = $this->taskProcessingManager->getNextScheduledTask($possibleTaskTypeIds, $taskIdsToIgnore);
+ try {
+ $provider = $this->taskProcessingManager->getPreferredProvider($task->getTaskTypeId());
+ if (in_array($provider->getId(), $possibleProviderIds, true)) {
+ if ($this->taskProcessingManager->lockTask($task)) {
+ break;
+ }
+ }
+ } catch (Exception) {
+ // There is no provider set for the task type of this task
+ // proceed to ignore this task
+ }
+
+ $taskIdsToIgnore[] = (int)$task->getId();
+ }
+
+ /** @var CoreTaskProcessingTask $json */
+ $json = $task->jsonSerialize();
+
+ return new DataResponse([
+ 'task' => $json,
+ 'provider' => [
+ 'name' => $provider->getId(),
+ ],
+ ]);
+ } catch (NotFoundException) {
+ return new DataResponse(null, Http::STATUS_NO_CONTENT);
+ } catch (Exception) {
+ return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ /**
+ * @param resource $data
+ * @return int
+ * @throws NotPermittedException
+ */
+ private function setFileContentsInternal($data): int {
+ try {
+ $folder = $this->appData->getFolder('TaskProcessing');
+ } catch (\OCP\Files\NotFoundException) {
+ $folder = $this->appData->newFolder('TaskProcessing');
+ }
+ /** @var SimpleFile $file */
+ $file = $folder->newFile(time() . '-' . rand(1, 100000), $data);
+ return $file->getId();
+ }
+}
diff --git a/core/Controller/TeamsApiController.php b/core/Controller/TeamsApiController.php
index f937bcb847c..2eb33a0c254 100644
--- a/core/Controller/TeamsApiController.php
+++ b/core/Controller/TeamsApiController.php
@@ -2,33 +2,18 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2024 Julius Härtl <jus@bitgrid.net>
- *
- * @author Julius Härtl <jus@bitgrid.net>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
-use OCA\Core\ResponseDefinitions;
+use OC\Core\ResponseDefinitions;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCSController;
use OCP\IRequest;
use OCP\Teams\ITeamManager;
use OCP\Teams\Team;
@@ -38,7 +23,7 @@ use OCP\Teams\Team;
* @psalm-import-type CoreTeam from ResponseDefinitions
* @property $userId string
*/
-class TeamsApiController extends \OCP\AppFramework\OCSController {
+class TeamsApiController extends OCSController {
public function __construct(
string $appName,
IRequest $request,
@@ -52,7 +37,7 @@ class TeamsApiController extends \OCP\AppFramework\OCSController {
* Get all resources of a team
*
* @param string $teamId Unique id of the team
- * @return DataResponse<Http::STATUS_OK, array{resources: CoreTeamResource[]}, array{}>
+ * @return DataResponse<Http::STATUS_OK, array{resources: list<CoreTeamResource>}, array{}>
*
* 200: Resources returned
*/
@@ -60,7 +45,7 @@ class TeamsApiController extends \OCP\AppFramework\OCSController {
#[ApiRoute(verb: 'GET', url: '/{teamId}/resources', root: '/teams')]
public function resolveOne(string $teamId): DataResponse {
/**
- * @var CoreTeamResource[] $resolvedResources
+ * @var list<CoreTeamResource> $resolvedResources
* @psalm-suppress PossiblyNullArgument The route is limited to logged-in users
*/
$resolvedResources = $this->teamManager->getSharedWith($teamId, $this->userId);
@@ -73,7 +58,7 @@ class TeamsApiController extends \OCP\AppFramework\OCSController {
*
* @param string $providerId Identifier of the provider (e.g. deck, talk, collectives)
* @param string $resourceId Unique id of the resource to list teams for (e.g. deck board id)
- * @return DataResponse<Http::STATUS_OK, array{teams: CoreTeam[]}, array{}>
+ * @return DataResponse<Http::STATUS_OK, array{teams: list<CoreTeam>}, array{}>
*
* 200: Teams returned
*/
@@ -82,13 +67,13 @@ class TeamsApiController extends \OCP\AppFramework\OCSController {
public function listTeams(string $providerId, string $resourceId): DataResponse {
/** @psalm-suppress PossiblyNullArgument The route is limited to logged-in users */
$teams = $this->teamManager->getTeamsForResource($providerId, $resourceId, $this->userId);
- /** @var CoreTeam[] $teams */
- $teams = array_map(function (Team $team) {
+ /** @var list<CoreTeam> $teams */
+ $teams = array_values(array_map(function (Team $team) {
$response = $team->jsonSerialize();
/** @psalm-suppress PossiblyNullArgument The route is limited to logged in users */
$response['resources'] = $this->teamManager->getSharedWith($team->getId(), $this->userId);
return $response;
- }, $teams);
+ }, $teams));
return new DataResponse([
'teams' => $teams,
diff --git a/core/Controller/TextProcessingApiController.php b/core/Controller/TextProcessingApiController.php
index 6ba98f99f51..d3e6967f169 100644
--- a/core/Controller/TextProcessingApiController.php
+++ b/core/Controller/TextProcessingApiController.php
@@ -3,31 +3,15 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net>
- *
- * @author Marcel Klehr <mklehr@gmx.net>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
use InvalidArgumentException;
-use OCA\Core\ResponseDefinitions;
+use OC\Core\ResponseDefinitions;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\AnonRateLimit;
use OCP\AppFramework\Http\Attribute\ApiRoute;
@@ -35,6 +19,7 @@ use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\Attribute\UserRateLimit;
use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCSController;
use OCP\Common\Exception\NotFoundException;
use OCP\DB\Exception;
use OCP\IL10N;
@@ -52,15 +37,15 @@ use Psr\Log\LoggerInterface;
/**
* @psalm-import-type CoreTextProcessingTask from ResponseDefinitions
*/
-class TextProcessingApiController extends \OCP\AppFramework\OCSController {
+class TextProcessingApiController extends OCSController {
public function __construct(
- string $appName,
- IRequest $request,
- private IManager $textProcessingManager,
- private IL10N $l,
- private ?string $userId,
+ string $appName,
+ IRequest $request,
+ private IManager $textProcessingManager,
+ private IL10N $l,
+ private ?string $userId,
private ContainerInterface $container,
- private LoggerInterface $logger,
+ private LoggerInterface $logger,
) {
parent::__construct($appName, $request);
}
@@ -68,7 +53,7 @@ class TextProcessingApiController extends \OCP\AppFramework\OCSController {
/**
* This endpoint returns all available LanguageModel task types
*
- * @return DataResponse<Http::STATUS_OK, array{types: array{id: string, name: string, description: string}[]}, array{}>
+ * @return DataResponse<Http::STATUS_OK, array{types: list<array{id: string, name: string, description: string}>}, array{}>
*
* 200: Task types returned
*/
@@ -125,7 +110,7 @@ class TextProcessingApiController extends \OCP\AppFramework\OCSController {
try {
try {
$this->textProcessingManager->runOrScheduleTask($task);
- } catch(TaskFailureException) {
+ } catch (TaskFailureException) {
// noop, because the task object has the failure status set already, we just return the task json
}
@@ -207,19 +192,18 @@ class TextProcessingApiController extends \OCP\AppFramework\OCSController {
*
* @param string $appId ID of the app
* @param string|null $identifier An arbitrary identifier for the task
- * @return DataResponse<Http::STATUS_OK, array{tasks: CoreTextProcessingTask[]}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
+ * @return DataResponse<Http::STATUS_OK, array{tasks: list<CoreTextProcessingTask>}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
*
- * 200: Task list returned
+ * 200: Task list returned
*/
#[NoAdminRequired]
#[ApiRoute(verb: 'GET', url: '/tasks/app/{appId}', root: '/textprocessing')]
public function listTasksByApp(string $appId, ?string $identifier = null): DataResponse {
try {
$tasks = $this->textProcessingManager->getUserTasksByApp($this->userId, $appId, $identifier);
- /** @var CoreTextProcessingTask[] $json */
- $json = array_map(static function (Task $task) {
+ $json = array_values(array_map(static function (Task $task) {
return $task->jsonSerialize();
- }, $tasks);
+ }, $tasks));
return new DataResponse([
'tasks' => $json,
diff --git a/core/Controller/TextToImageApiController.php b/core/Controller/TextToImageApiController.php
index 8dd21e90664..d2c3e1ec288 100644
--- a/core/Controller/TextToImageApiController.php
+++ b/core/Controller/TextToImageApiController.php
@@ -3,31 +3,15 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net>
- *
- * @author Marcel Klehr <mklehr@gmx.net>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
+use OC\Core\ResponseDefinitions;
use OC\Files\AppData\AppData;
-use OCA\Core\ResponseDefinitions;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\AnonRateLimit;
use OCP\AppFramework\Http\Attribute\ApiRoute;
@@ -37,6 +21,7 @@ use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\Attribute\UserRateLimit;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\FileDisplayResponse;
+use OCP\AppFramework\OCSController;
use OCP\DB\Exception;
use OCP\Files\NotFoundException;
use OCP\IL10N;
@@ -50,7 +35,7 @@ use OCP\TextToImage\Task;
/**
* @psalm-import-type CoreTextToImageTask from ResponseDefinitions
*/
-class TextToImageApiController extends \OCP\AppFramework\OCSController {
+class TextToImageApiController extends OCSController {
public function __construct(
string $appName,
IRequest $request,
@@ -166,12 +151,12 @@ class TextToImageApiController extends \OCP\AppFramework\OCSController {
$task = $this->textToImageManager->getUserTask($id, $this->userId);
try {
$folder = $this->appData->getFolder('text2image');
- } catch(NotFoundException) {
+ } catch (NotFoundException) {
$res = new DataResponse(['message' => $this->l->t('Image not found')], Http::STATUS_NOT_FOUND);
$res->throttle(['action' => 'text2image']);
return $res;
}
- $file = $folder->getFolder((string) $task->getId())->getFile((string) $index);
+ $file = $folder->getFolder((string)$task->getId())->getFile((string)$index);
$info = getimagesizefromstring($file->getContent());
return new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => image_type_to_mime_type($info[2])]);
@@ -228,9 +213,9 @@ class TextToImageApiController extends \OCP\AppFramework\OCSController {
*
* @param string $appId ID of the app
* @param string|null $identifier An arbitrary identifier for the task
- * @return DataResponse<Http::STATUS_OK, array{tasks: CoreTextToImageTask[]}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
+ * @return DataResponse<Http::STATUS_OK, array{tasks: list<CoreTextToImageTask>}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
*
- * 200: Task list returned
+ * 200: Task list returned
*/
#[NoAdminRequired]
#[AnonRateLimit(limit: 5, period: 120)]
@@ -238,10 +223,9 @@ class TextToImageApiController extends \OCP\AppFramework\OCSController {
public function listTasksByApp(string $appId, ?string $identifier = null): DataResponse {
try {
$tasks = $this->textToImageManager->getUserTasksByApp($this->userId, $appId, $identifier);
- /** @var CoreTextToImageTask[] $json */
- $json = array_map(static function (Task $task) {
+ $json = array_values(array_map(static function (Task $task) {
return $task->jsonSerialize();
- }, $tasks);
+ }, $tasks));
return new DataResponse([
'tasks' => $json,
diff --git a/core/Controller/TranslationApiController.php b/core/Controller/TranslationApiController.php
index 4cc0ec95ca1..73dd0657230 100644
--- a/core/Controller/TranslationApiController.php
+++ b/core/Controller/TranslationApiController.php
@@ -3,25 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2022 Julius Härtl <jus@bitgrid.net>
- *
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -29,15 +12,19 @@ namespace OC\Core\Controller;
use InvalidArgumentException;
use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\AnonRateLimit;
use OCP\AppFramework\Http\Attribute\ApiRoute;
+use OCP\AppFramework\Http\Attribute\PublicPage;
+use OCP\AppFramework\Http\Attribute\UserRateLimit;
use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCSController;
use OCP\IL10N;
use OCP\IRequest;
use OCP\PreConditionNotMetException;
use OCP\Translation\CouldNotTranslateException;
use OCP\Translation\ITranslationManager;
-class TranslationApiController extends \OCP\AppFramework\OCSController {
+class TranslationApiController extends OCSController {
public function __construct(
string $appName,
IRequest $request,
@@ -48,27 +35,22 @@ class TranslationApiController extends \OCP\AppFramework\OCSController {
}
/**
- * @PublicPage
- *
* Get the list of supported languages
*
- * @return DataResponse<Http::STATUS_OK, array{languages: array{from: string, fromLabel: string, to: string, toLabel: string}[], languageDetection: bool}, array{}>
+ * @return DataResponse<Http::STATUS_OK, array{languages: list<array{from: string, fromLabel: string, to: string, toLabel: string}>, languageDetection: bool}, array{}>
*
* 200: Supported languages returned
*/
+ #[PublicPage]
#[ApiRoute(verb: 'GET', url: '/languages', root: '/translation')]
public function languages(): DataResponse {
return new DataResponse([
- 'languages' => array_map(fn ($lang) => $lang->jsonSerialize(), $this->translationManager->getLanguages()),
+ 'languages' => array_values(array_map(fn ($lang) => $lang->jsonSerialize(), $this->translationManager->getLanguages())),
'languageDetection' => $this->translationManager->canDetectLanguage(),
]);
}
/**
- * @PublicPage
- * @UserRateThrottle(limit=25, period=120)
- * @AnonRateThrottle(limit=10, period=120)
- *
* Translate a text
*
* @param string $text Text to be translated
@@ -80,6 +62,9 @@ class TranslationApiController extends \OCP\AppFramework\OCSController {
* 400: Language not detected or unable to translate
* 412: Translating is not possible
*/
+ #[PublicPage]
+ #[UserRateLimit(limit: 25, period: 120)]
+ #[AnonRateLimit(limit: 10, period: 120)]
#[ApiRoute(verb: 'POST', url: '/translate', root: '/translation')]
public function translate(string $text, ?string $fromLanguage, string $toLanguage): DataResponse {
try {
diff --git a/core/Controller/TwoFactorApiController.php b/core/Controller/TwoFactorApiController.php
new file mode 100644
index 00000000000..8d89963e6ad
--- /dev/null
+++ b/core/Controller/TwoFactorApiController.php
@@ -0,0 +1,99 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Controller;
+
+use OC\Authentication\TwoFactorAuth\ProviderManager;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\ApiRoute;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCSController;
+use OCP\Authentication\TwoFactorAuth\IRegistry;
+use OCP\IRequest;
+use OCP\IUserManager;
+
+class TwoFactorApiController extends OCSController {
+ public function __construct(
+ string $appName,
+ IRequest $request,
+ private ProviderManager $tfManager,
+ private IRegistry $tfRegistry,
+ private IUserManager $userManager,
+ ) {
+ parent::__construct($appName, $request);
+ }
+
+ /**
+ * Get two factor authentication provider states
+ *
+ * @param string $user system user id
+ *
+ * @return DataResponse<Http::STATUS_OK, array<string, bool>, array{}>|DataResponse<Http::STATUS_NOT_FOUND, null, array{}>
+ *
+ * 200: provider states
+ * 404: user not found
+ */
+ #[ApiRoute(verb: 'GET', url: '/state', root: '/twofactor')]
+ public function state(string $user): DataResponse {
+ $userObject = $this->userManager->get($user);
+ if ($userObject !== null) {
+ $state = $this->tfRegistry->getProviderStates($userObject);
+ return new DataResponse($state);
+ }
+ return new DataResponse(null, Http::STATUS_NOT_FOUND);
+ }
+
+ /**
+ * Enable two factor authentication providers for specific user
+ *
+ * @param string $user system user identifier
+ * @param list<string> $providers collection of TFA provider ids
+ *
+ * @return DataResponse<Http::STATUS_OK, array<string, bool>, array{}>|DataResponse<Http::STATUS_NOT_FOUND, null, array{}>
+ *
+ * 200: provider states
+ * 404: user not found
+ */
+ #[ApiRoute(verb: 'POST', url: '/enable', root: '/twofactor')]
+ public function enable(string $user, array $providers = []): DataResponse {
+ $userObject = $this->userManager->get($user);
+ if ($userObject !== null) {
+ foreach ($providers as $providerId) {
+ $this->tfManager->tryEnableProviderFor($providerId, $userObject);
+ }
+ $state = $this->tfRegistry->getProviderStates($userObject);
+ return new DataResponse($state);
+ }
+ return new DataResponse(null, Http::STATUS_NOT_FOUND);
+ }
+
+ /**
+ * Disable two factor authentication providers for specific user
+ *
+ * @param string $user system user identifier
+ * @param list<string> $providers collection of TFA provider ids
+ *
+ * @return DataResponse<Http::STATUS_OK, array<string, bool>, array{}>|DataResponse<Http::STATUS_NOT_FOUND, null, array{}>
+ *
+ * 200: provider states
+ * 404: user not found
+ */
+ #[ApiRoute(verb: 'POST', url: '/disable', root: '/twofactor')]
+ public function disable(string $user, array $providers = []): DataResponse {
+ $userObject = $this->userManager->get($user);
+ if ($userObject !== null) {
+ foreach ($providers as $providerId) {
+ $this->tfManager->tryDisableProviderFor($providerId, $userObject);
+ }
+ $state = $this->tfRegistry->getProviderStates($userObject);
+ return new DataResponse($state);
+ }
+ return new DataResponse(null, Http::STATUS_NOT_FOUND);
+ }
+
+}
diff --git a/core/Controller/TwoFactorChallengeController.php b/core/Controller/TwoFactorChallengeController.php
index 48fd365fd12..4791139bb12 100644
--- a/core/Controller/TwoFactorChallengeController.php
+++ b/core/Controller/TwoFactorChallengeController.php
@@ -1,28 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Cornelius Kölbel <cornelius.koelbel@netknights.it>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Controller;
@@ -30,6 +11,8 @@ use OC\Authentication\TwoFactorAuth\Manager;
use OC_User;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\Attribute\UseSession;
use OCP\AppFramework\Http\RedirectResponse;
@@ -42,6 +25,7 @@ use OCP\IRequest;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUserSession;
+use OCP\Util;
use Psr\Log\LoggerInterface;
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
@@ -83,13 +67,13 @@ class TwoFactorChallengeController extends Controller {
}
/**
- * @NoAdminRequired
- * @NoCSRFRequired
* @TwoFactorSetUpDoneRequired
*
* @param string $redirect_url
* @return StandaloneTemplateResponse
*/
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/login/selectchallenge')]
public function selectChallenge($redirect_url) {
$user = $this->userSession->getUser();
@@ -106,18 +90,19 @@ class TwoFactorChallengeController extends Controller {
'logout_url' => $this->getLogoutUrl(),
'hasSetupProviders' => !empty($setupProviders),
];
+ Util::addScript('core', 'twofactor-request-token');
return new StandaloneTemplateResponse($this->appName, 'twofactorselectchallenge', $data, 'guest');
}
/**
- * @NoAdminRequired
- * @NoCSRFRequired
* @TwoFactorSetUpDoneRequired
*
* @param string $challengeProviderId
* @param string $redirect_url
* @return StandaloneTemplateResponse|RedirectResponse
*/
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
#[UseSession]
#[FrontpageRoute(verb: 'GET', url: '/login/challenge/{challengeProviderId}')]
public function showChallenge($challengeProviderId, $redirect_url) {
@@ -140,7 +125,7 @@ class TwoFactorChallengeController extends Controller {
if ($this->session->exists('two_factor_auth_error')) {
$this->session->remove('two_factor_auth_error');
$error = true;
- $errorMessage = $this->session->get("two_factor_auth_error_message");
+ $errorMessage = $this->session->get('two_factor_auth_error_message');
$this->session->remove('two_factor_auth_error_message');
}
$tmpl = $provider->getTemplate($user);
@@ -158,12 +143,11 @@ class TwoFactorChallengeController extends Controller {
if ($provider instanceof IProvidesCustomCSP) {
$response->setContentSecurityPolicy($provider->getCSP());
}
+ Util::addScript('core', 'twofactor-request-token');
return $response;
}
/**
- * @NoAdminRequired
- * @NoCSRFRequired
* @TwoFactorSetUpDoneRequired
*
* @UserRateThrottle(limit=5, period=100)
@@ -173,6 +157,8 @@ class TwoFactorChallengeController extends Controller {
* @param string $redirect_url
* @return RedirectResponse
*/
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
#[UseSession]
#[FrontpageRoute(verb: 'POST', url: '/login/challenge/{challengeProviderId}')]
public function solveChallenge($challengeProviderId, $challenge, $redirect_url = null) {
@@ -208,10 +194,8 @@ class TwoFactorChallengeController extends Controller {
]));
}
- /**
- * @NoAdminRequired
- * @NoCSRFRequired
- */
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: 'login/setupchallenge')]
public function setupProviders(?string $redirect_url = null): StandaloneTemplateResponse {
$user = $this->userSession->getUser();
@@ -223,13 +207,12 @@ class TwoFactorChallengeController extends Controller {
'redirect_url' => $redirect_url,
];
+ Util::addScript('core', 'twofactor-request-token');
return new StandaloneTemplateResponse($this->appName, 'twofactorsetupselection', $data, 'guest');
}
- /**
- * @NoAdminRequired
- * @NoCSRFRequired
- */
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: 'login/setupchallenge/{providerId}')]
public function setupProvider(string $providerId, ?string $redirect_url = null) {
$user = $this->userSession->getUser();
@@ -256,15 +239,15 @@ class TwoFactorChallengeController extends Controller {
'template' => $tmpl->fetchPage(),
];
$response = new StandaloneTemplateResponse($this->appName, 'twofactorsetupchallenge', $data, 'guest');
+ Util::addScript('core', 'twofactor-request-token');
return $response;
}
/**
- * @NoAdminRequired
- * @NoCSRFRequired
- *
* @todo handle the extreme edge case of an invalid provider ID and redirect to the provider selection page
*/
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
#[FrontpageRoute(verb: 'POST', url: 'login/setupchallenge/{providerId}')]
public function confirmProviderSetup(string $providerId, ?string $redirect_url = null) {
return new RedirectResponse($this->urlGenerator->linkToRoute(
diff --git a/core/Controller/UnifiedSearchController.php b/core/Controller/UnifiedSearchController.php
index 469c6c6ed7b..c770c6240df 100644
--- a/core/Controller/UnifiedSearchController.php
+++ b/core/Controller/UnifiedSearchController.php
@@ -3,38 +3,20 @@
declare(strict_types=1);
/**
- * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
use InvalidArgumentException;
+use OC\Core\ResponseDefinitions;
use OC\Search\SearchComposer;
use OC\Search\SearchQuery;
use OC\Search\UnsupportedFilter;
-use OCA\Core\ResponseDefinitions;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
@@ -60,16 +42,15 @@ class UnifiedSearchController extends OCSController {
}
/**
- * @NoAdminRequired
- * @NoCSRFRequired
- *
* Get the providers for unified search
*
* @param string $from the url the user is currently at
- * @return DataResponse<Http::STATUS_OK, CoreUnifiedSearchProvider[], array{}>
+ * @return DataResponse<Http::STATUS_OK, list<CoreUnifiedSearchProvider>, array{}>
*
* 200: Providers returned
*/
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
#[ApiRoute(verb: 'GET', url: '/providers', root: '/search')]
public function getProviders(string $from = ''): DataResponse {
[$route, $parameters] = $this->getRouteInformation($from);
@@ -81,9 +62,6 @@ class UnifiedSearchController extends OCSController {
}
/**
- * @NoAdminRequired
- * @NoCSRFRequired
- *
* Launch a search for a specific search provider.
*
* Additional filters are available for each provider.
@@ -92,7 +70,7 @@ class UnifiedSearchController extends OCSController {
* @param string $providerId ID of the provider
* @param string $term Term to search
* @param int|null $sortOrder Order of entries
- * @param int|null $limit Maximum amount of entries
+ * @param int|null $limit Maximum amount of entries, limited to 25
* @param int|string|null $cursor Offset for searching
* @param string $from The current user URL
*
@@ -101,6 +79,8 @@ class UnifiedSearchController extends OCSController {
* 200: Search entries returned
* 400: Searching is not possible
*/
+ #[NoAdminRequired]
+ #[NoCSRFRequired]
#[ApiRoute(verb: 'GET', url: '/providers/{providerId}/search', root: '/search')]
public function search(
string $providerId,
@@ -113,6 +93,9 @@ class UnifiedSearchController extends OCSController {
): DataResponse {
[$route, $routeParameters] = $this->getRouteInformation($from);
+ $limit ??= SearchQuery::LIMIT_DEFAULT;
+ $limit = max(1, min($limit, 25));
+
try {
$filters = $this->composer->buildFilterList($providerId, $this->request->getParams());
} catch (UnsupportedFilter|InvalidArgumentException $e) {
@@ -125,7 +108,7 @@ class UnifiedSearchController extends OCSController {
new SearchQuery(
$filters,
$sortOrder ?? ISearchQuery::SORT_DATE_DESC,
- $limit ?? SearchQuery::LIMIT_DEFAULT,
+ $limit,
$cursor,
$route,
$routeParameters
diff --git a/core/Controller/UnsupportedBrowserController.php b/core/Controller/UnsupportedBrowserController.php
index dfcff8df381..2877e2e9047 100644
--- a/core/Controller/UnsupportedBrowserController.php
+++ b/core/Controller/UnsupportedBrowserController.php
@@ -3,33 +3,17 @@
declare(strict_types=1);
/**
- * @copyright 2021 John Molakvoæ <skjnldsv@protonmail.com>
- *
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IRequest;
@@ -42,15 +26,16 @@ class UnsupportedBrowserController extends Controller {
}
/**
- * @PublicPage
- * @NoCSRFRequired
- *
* @return Response
*/
+ #[PublicPage]
+ #[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: 'unsupported')]
public function index(): Response {
Util::addScript('core', 'unsupported-browser');
Util::addStyle('core', 'icons');
- return new TemplateResponse('core', 'unsupportedbrowser', [], TemplateResponse::RENDER_AS_ERROR);
+
+ // not using RENDER_AS_ERROR as we need the JSConfigHelper for url generation
+ return new TemplateResponse('core', 'unsupportedbrowser', [], TemplateResponse::RENDER_AS_GUEST);
}
}
diff --git a/core/Controller/UserController.php b/core/Controller/UserController.php
index c941a80e53d..b6e464d9a95 100644
--- a/core/Controller/UserController.php
+++ b/core/Controller/UserController.php
@@ -1,31 +1,15 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Core\Controller;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
use OCP\IUserManager;
@@ -42,12 +26,11 @@ class UserController extends Controller {
/**
* Lookup user display names
*
- * @NoAdminRequired
- *
* @param array $users
*
* @return JSONResponse
*/
+ #[NoAdminRequired]
#[FrontpageRoute(verb: 'POST', url: '/displaynames')]
public function getDisplayNames($users) {
$result = [];
diff --git a/core/Controller/WalledGardenController.php b/core/Controller/WalledGardenController.php
index e5d8edd9083..d0bc0665534 100644
--- a/core/Controller/WalledGardenController.php
+++ b/core/Controller/WalledGardenController.php
@@ -1,41 +1,23 @@
<?php
+
/**
- * @copyright 2017, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\Response;
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class WalledGardenController extends Controller {
- /**
- * @PublicPage
- * @NoCSRFRequired
- */
+ #[PublicPage]
+ #[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '/204')]
public function get(): Response {
$resp = new Response();
diff --git a/core/Controller/WebAuthnController.php b/core/Controller/WebAuthnController.php
index 70034f08fcc..d7255831e88 100644
--- a/core/Controller/WebAuthnController.php
+++ b/core/Controller/WebAuthnController.php
@@ -3,27 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Richard Steinmetz <richard@steinmetz.cloud>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
@@ -34,6 +15,7 @@ use OC\URLGenerator;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\Attribute\UseSession;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
@@ -58,10 +40,7 @@ class WebAuthnController extends Controller {
parent::__construct($appName, $request);
}
- /**
- * @NoAdminRequired
- * @PublicPage
- */
+ #[PublicPage]
#[UseSession]
#[FrontpageRoute(verb: 'POST', url: 'login/webauthn/start')]
public function startAuthentication(string $loginName): JSONResponse {
@@ -83,10 +62,7 @@ class WebAuthnController extends Controller {
return new JSONResponse($publicKeyCredentialRequestOptions);
}
- /**
- * @NoAdminRequired
- * @PublicPage
- */
+ #[PublicPage]
#[UseSession]
#[FrontpageRoute(verb: 'POST', url: 'login/webauthn/finish')]
public function finishAuthentication(string $data): JSONResponse {
diff --git a/core/Controller/WellKnownController.php b/core/Controller/WellKnownController.php
index 0e6b7ee3ef8..9ce83686355 100644
--- a/core/Controller/WellKnownController.php
+++ b/core/Controller/WellKnownController.php
@@ -3,26 +3,8 @@
declare(strict_types=1);
/**
- * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
@@ -30,7 +12,9 @@ use OC\Http\WellKnown\RequestManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Response;
use OCP\IRequest;
@@ -45,11 +29,10 @@ class WellKnownController extends Controller {
}
/**
- * @PublicPage
- * @NoCSRFRequired
- *
* @return Response
*/
+ #[PublicPage]
+ #[NoCSRFRequired]
#[FrontpageRoute(verb: 'GET', url: '.well-known/{service}')]
public function handle(string $service): Response {
$response = $this->requestManager->process(
@@ -58,7 +41,7 @@ class WellKnownController extends Controller {
);
if ($response === null) {
- $httpResponse = new JSONResponse(["message" => "$service not supported"], Http::STATUS_NOT_FOUND);
+ $httpResponse = new JSONResponse(['message' => "$service not supported"], Http::STATUS_NOT_FOUND);
} else {
$httpResponse = $response->toHttpResponse();
}
diff --git a/core/Controller/WhatsNewController.php b/core/Controller/WhatsNewController.php
index ab107cd115d..af8c3d4853b 100644
--- a/core/Controller/WhatsNewController.php
+++ b/core/Controller/WhatsNewController.php
@@ -1,26 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2018 Arthur Schiwon <blizzz@arthur-schiwon.de>
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
@@ -30,6 +12,7 @@ use OC\Updater\ChangesCheck;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\Defaults;
use OCP\IConfig;
@@ -37,6 +20,8 @@ use OCP\IRequest;
use OCP\IUserManager;
use OCP\IUserSession;
use OCP\L10N\IFactory;
+use OCP\PreConditionNotMetException;
+use OCP\ServerVersion;
class WhatsNewController extends OCSController {
public function __construct(
@@ -46,29 +31,29 @@ class WhatsNewController extends OCSController {
private IUserSession $userSession,
IUserManager $userManager,
Manager $keyManager,
+ ServerVersion $serverVersion,
private IConfig $config,
private ChangesCheck $whatsNewService,
private IFactory $langFactory,
private Defaults $defaults,
) {
- parent::__construct($appName, $request, $capabilitiesManager, $userSession, $userManager, $keyManager);
+ parent::__construct($appName, $request, $capabilitiesManager, $userSession, $userManager, $keyManager, $serverVersion);
}
/**
- * @NoAdminRequired
- *
* Get the changes
*
- * @return DataResponse<Http::STATUS_OK, array{changelogURL: string, product: string, version: string, whatsNew?: array{regular: string[], admin: string[]}}, array{}>|DataResponse<Http::STATUS_NO_CONTENT, array<empty>, array{}>
+ * @return DataResponse<Http::STATUS_OK, array{changelogURL: string, product: string, version: string, whatsNew?: array{regular: list<string>, admin: list<string>}}, array{}>|DataResponse<Http::STATUS_NO_CONTENT, list<empty>, array{}>
*
* 200: Changes returned
* 204: No changes
*/
+ #[NoAdminRequired]
#[ApiRoute(verb: 'GET', url: '/whatsnew', root: '/core')]
public function get():DataResponse {
$user = $this->userSession->getUser();
if ($user === null) {
- throw new \RuntimeException("Acting user cannot be resolved");
+ throw new \RuntimeException('Acting user cannot be resolved');
}
$lastRead = $this->config->getUserValue($user->getUID(), 'core', 'whatsNewLastRead', 0);
$currentVersion = $this->whatsNewService->normalizeVersion($this->config->getSystemValue('version'));
@@ -100,23 +85,22 @@ class WhatsNewController extends OCSController {
}
/**
- * @NoAdminRequired
- *
* Dismiss the changes
*
* @param string $version Version to dismiss the changes for
*
- * @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
- * @throws \OCP\PreConditionNotMetException
+ * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
+ * @throws PreConditionNotMetException
* @throws DoesNotExistException
*
* 200: Changes dismissed
*/
+ #[NoAdminRequired]
#[ApiRoute(verb: 'POST', url: '/whatsnew', root: '/core')]
public function dismiss(string $version):DataResponse {
$user = $this->userSession->getUser();
if ($user === null) {
- throw new \RuntimeException("Acting user cannot be resolved");
+ throw new \RuntimeException('Acting user cannot be resolved');
}
$version = $this->whatsNewService->normalizeVersion($version);
// checks whether it's a valid version, throws an Exception otherwise
diff --git a/core/Controller/WipeController.php b/core/Controller/WipeController.php
index 3e486003668..1b57be71aa0 100644
--- a/core/Controller/WipeController.php
+++ b/core/Controller/WipeController.php
@@ -3,37 +3,24 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Kate Döen <kate.doeen@nextcloud.com>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
use OC\Authentication\Token\RemoteWipe;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\AnonRateLimit;
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\OpenAPI;
+use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\JSONResponse;
use OCP\Authentication\Exceptions\InvalidTokenException;
use OCP\IRequest;
+#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
class WipeController extends Controller {
public function __construct(
string $appName,
@@ -44,21 +31,18 @@ class WipeController extends Controller {
}
/**
- * @NoAdminRequired
- * @NoCSRFRequired
- * @PublicPage
- *
- * @AnonRateThrottle(limit=10, period=300)
- *
* Check if the device should be wiped
*
* @param string $token App password
*
- * @return JSONResponse<Http::STATUS_OK, array{wipe: bool}, array{}>|JSONResponse<Http::STATUS_NOT_FOUND, array<empty>, array{}>
+ * @return JSONResponse<Http::STATUS_OK, array{wipe: bool}, array{}>|JSONResponse<Http::STATUS_NOT_FOUND, list<empty>, array{}>
*
* 200: Device should be wiped
* 404: Device should not be wiped
*/
+ #[PublicPage]
+ #[NoCSRFRequired]
+ #[AnonRateLimit(limit: 10, period: 300)]
#[FrontpageRoute(verb: 'POST', url: '/core/wipe/check')]
public function checkWipe(string $token): JSONResponse {
try {
@@ -76,21 +60,18 @@ class WipeController extends Controller {
/**
- * @NoAdminRequired
- * @NoCSRFRequired
- * @PublicPage
- *
- * @AnonRateThrottle(limit=10, period=300)
- *
* Finish the wipe
*
* @param string $token App password
*
- * @return JSONResponse<Http::STATUS_OK|Http::STATUS_NOT_FOUND, array<empty>, array{}>
+ * @return JSONResponse<Http::STATUS_OK|Http::STATUS_NOT_FOUND, list<empty>, array{}>
*
* 200: Wipe finished successfully
* 404: Device should not be wiped
*/
+ #[PublicPage]
+ #[NoCSRFRequired]
+ #[AnonRateLimit(limit: 10, period: 300)]
#[FrontpageRoute(verb: 'POST', url: '/core/wipe/success')]
public function wipeDone(string $token): JSONResponse {
try {