diff options
Diffstat (limited to 'core/Controller/AppPasswordController.php')
-rw-r--r-- | core/Controller/AppPasswordController.php | 144 |
1 files changed, 82 insertions, 62 deletions
diff --git a/core/Controller/AppPasswordController.php b/core/Controller/AppPasswordController.php index 41f0f6e4f27..e5edc165bf5 100644 --- a/core/Controller/AppPasswordController.php +++ b/core/Controller/AppPasswordController.php @@ -3,83 +3,62 @@ 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> - * - * @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 OC\Authentication\Events\AppPasswordCreatedEvent; -use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Token\IProvider; 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; use OCP\Authentication\LoginCredentials\IStore; use OCP\EventDispatcher\IEventDispatcher; use OCP\IRequest; use OCP\ISession; +use OCP\IUserManager; +use OCP\Security\Bruteforce\IThrottler; use OCP\Security\ISecureRandom; -class AppPasswordController extends \OCP\AppFramework\OCSController { - - /** @var ISession */ - private $session; - - /** @var ISecureRandom */ - private $random; - - /** @var IProvider */ - private $tokenProvider; - - /** @var IStore */ - private $credentialStore; - - /** @var IEventDispatcher */ - private $eventDispatcher; - - public function __construct(string $appName, - IRequest $request, - ISession $session, - ISecureRandom $random, - IProvider $tokenProvider, - IStore $credentialStore, - IEventDispatcher $eventDispatcher) { +class AppPasswordController extends OCSController { + public function __construct( + string $appName, + IRequest $request, + private ISession $session, + private ISecureRandom $random, + private IProvider $tokenProvider, + private IStore $credentialStore, + private IEventDispatcher $eventDispatcher, + private Session $userSession, + private IUserManager $userManager, + private IThrottler $throttler, + ) { parent::__construct($appName, $request); - - $this->session = $session; - $this->random = $random; - $this->tokenProvider = $tokenProvider; - $this->credentialStore = $credentialStore; - $this->eventDispatcher = $eventDispatcher; } /** - * @NoAdminRequired + * Create app password + * + * @return DataResponse<Http::STATUS_OK, array{apppassword: string}, array{}> + * @throws OCSForbiddenException Creating app password is not allowed * - * @return DataResponse - * @throws OCSForbiddenException + * 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 if ($this->session->exists('app_password')) { @@ -98,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, @@ -122,11 +101,16 @@ class AppPasswordController extends \OCP\AppFramework\OCSController { } /** - * @NoAdminRequired + * Delete app password * - * @return DataResponse + * @return DataResponse<Http::STATUS_OK, list<empty>, array{}> + * @throws OCSForbiddenException Deleting app password is not allowed + * + * 200: App password deleted successfully */ - public function deleteAppPassword() { + #[NoAdminRequired] + #[ApiRoute(verb: 'DELETE', url: '/apppassword', root: '/core')] + public function deleteAppPassword(): DataResponse { if (!$this->session->exists('app_password')) { throw new OCSForbiddenException('no app password in use'); } @@ -144,8 +128,15 @@ class AppPasswordController extends \OCP\AppFramework\OCSController { } /** - * @NoAdminRequired + * Rotate app password + * + * @return DataResponse<Http::STATUS_OK, array{apppassword: string}, array{}> + * @throws OCSForbiddenException Rotating app password is not allowed + * + * 200: App password returned */ + #[NoAdminRequired] + #[ApiRoute(verb: 'POST', url: '/apppassword/rotate', root: '/core')] public function rotateAppPassword(): DataResponse { if (!$this->session->exists('app_password')) { throw new OCSForbiddenException('no app password in use'); @@ -159,11 +150,40 @@ 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([ 'apppassword' => $newToken, ]); } + + /** + * Confirm the user password + * + * @param string $password The password of the user + * + * @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 { + $loginName = $this->userSession->getLoginName(); + $loginResult = $this->userManager->checkPassword($loginName, $password); + if ($loginResult === false) { + $response = new DataResponse([], Http::STATUS_FORBIDDEN); + $response->throttle(['loginName' => $loginName]); + return $response; + } + + $confirmTimestamp = time(); + $this->session->set('last-password-confirm', $confirmTimestamp); + $this->throttler->resetDelay($this->request->getRemoteAddress(), 'sudo', ['loginName' => $loginName]); + return new DataResponse(['lastLogin' => $confirmTimestamp], Http::STATUS_OK); + } } |