]> source.dussan.org Git - nextcloud-server.git/commitdiff
feat(core): Add OCS endpoint for confirming the user password 43668/head
authorprovokateurin <kate@provokateurin.de>
Tue, 20 Feb 2024 08:32:33 +0000 (09:32 +0100)
committerprovokateurin <kate@provokateurin.de>
Tue, 20 Feb 2024 13:28:00 +0000 (14:28 +0100)
Signed-off-by: provokateurin <kate@provokateurin.de>
core/Controller/AppPasswordController.php
core/openapi.json
core/routes.php
tests/Core/Controller/AppPasswordControllerTest.php

index a4b7791997ab8778f36893234b6aa276f9e9a473..2575729fe850cf5da4cffa86869f99a4db7a9d18 100644 (file)
@@ -31,7 +31,9 @@ namespace OC\Core\Controller;
 use OC\Authentication\Events\AppPasswordCreatedEvent;
 use OC\Authentication\Token\IProvider;
 use OC\Authentication\Token\IToken;
+use OC\User\Session;
 use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\UseSession;
 use OCP\AppFramework\Http\DataResponse;
 use OCP\AppFramework\OCS\OCSForbiddenException;
 use OCP\Authentication\Exceptions\CredentialsUnavailableException;
@@ -41,6 +43,8 @@ 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 {
@@ -52,6 +56,9 @@ class AppPasswordController extends \OCP\AppFramework\OCSController {
                private IProvider $tokenProvider,
                private IStore $credentialStore,
                private IEventDispatcher $eventDispatcher,
+               private Session $userSession,
+               private IUserManager $userManager,
+               private IThrottler $throttler,
        ) {
                parent::__construct($appName, $request);
        }
@@ -165,4 +172,33 @@ class AppPasswordController extends \OCP\AppFramework\OCSController {
                        'apppassword' => $newToken,
                ]);
        }
+
+       /**
+        * 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{}>
+        *
+        * 200: Password confirmation succeeded
+        * 403: Password confirmation failed
+        */
+       #[UseSession]
+       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);
+       }
 }
index 9bfee0d40b937a3ac60df7b51f8a531f61cc1da1..5a33f547a923e9eeeb70507f3b409ba427c57f48 100644 (file)
                 }
             }
         },
+        "/ocs/v2.php/core/apppassword/confirm": {
+            "put": {
+                "operationId": "app_password-confirm-user-password",
+                "summary": "Confirm the user password",
+                "tags": [
+                    "app_password"
+                ],
+                "security": [
+                    {
+                        "bearer_auth": []
+                    },
+                    {
+                        "basic_auth": []
+                    }
+                ],
+                "parameters": [
+                    {
+                        "name": "password",
+                        "in": "query",
+                        "description": "The password of the user",
+                        "required": true,
+                        "schema": {
+                            "type": "string"
+                        }
+                    },
+                    {
+                        "name": "OCS-APIRequest",
+                        "in": "header",
+                        "description": "Required to be true for the API request to pass",
+                        "required": true,
+                        "schema": {
+                            "type": "boolean",
+                            "default": true
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "Password confirmation succeeded",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "type": "object",
+                                    "required": [
+                                        "ocs"
+                                    ],
+                                    "properties": {
+                                        "ocs": {
+                                            "type": "object",
+                                            "required": [
+                                                "meta",
+                                                "data"
+                                            ],
+                                            "properties": {
+                                                "meta": {
+                                                    "$ref": "#/components/schemas/OCSMeta"
+                                                },
+                                                "data": {
+                                                    "type": "object",
+                                                    "required": [
+                                                        "lastLogin"
+                                                    ],
+                                                    "properties": {
+                                                        "lastLogin": {
+                                                            "type": "integer",
+                                                            "format": "int64"
+                                                        }
+                                                    }
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    },
+                    "403": {
+                        "description": "Password confirmation failed",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "type": "object",
+                                    "required": [
+                                        "ocs"
+                                    ],
+                                    "properties": {
+                                        "ocs": {
+                                            "type": "object",
+                                            "required": [
+                                                "meta",
+                                                "data"
+                                            ],
+                                            "properties": {
+                                                "meta": {
+                                                    "$ref": "#/components/schemas/OCSMeta"
+                                                },
+                                                "data": {}
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        },
         "/ocs/v2.php/hovercard/v1/{userId}": {
             "get": {
                 "operationId": "hover_card-get-user",
index fe1fe6fcd75005a60f405061f78b1f5a804e0995..9f8bc5c2c1e1ec94271a8a5d65ca28e7b45e096e 100644 (file)
@@ -123,6 +123,7 @@ $application->registerRoutes($this, [
                ['root' => '/core', 'name' => 'AppPassword#getAppPassword', 'url' => '/getapppassword', 'verb' => 'GET'],
                ['root' => '/core', 'name' => 'AppPassword#rotateAppPassword', 'url' => '/apppassword/rotate', 'verb' => 'POST'],
                ['root' => '/core', 'name' => 'AppPassword#deleteAppPassword', 'url' => '/apppassword', 'verb' => 'DELETE'],
+               ['root' => '/core', 'name' => 'AppPassword#confirmUserPassword', 'url' => '/apppassword/confirm', 'verb' => 'PUT'],
 
                ['root' => '/hovercard', 'name' => 'HoverCard#getUser', 'url' => '/v1/{userId}', 'verb' => 'GET'],
 
index 47220fcf5aba08b17ce9db08fc78ac94840920d6..5f257d163ed26f748e7cdb1c02b81c964092ec8d 100644 (file)
@@ -29,6 +29,7 @@ use OC\Authentication\Exceptions\InvalidTokenException;
 use OC\Authentication\Token\IProvider;
 use OC\Authentication\Token\IToken;
 use OC\Core\Controller\AppPasswordController;
+use OC\User\Session;
 use OCP\AppFramework\Http\DataResponse;
 use OCP\AppFramework\OCS\OCSForbiddenException;
 use OCP\Authentication\Exceptions\CredentialsUnavailableException;
@@ -38,6 +39,8 @@ 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;
 use PHPUnit\Framework\MockObject\MockObject;
 use Test\TestCase;
@@ -61,6 +64,15 @@ class AppPasswordControllerTest extends TestCase {
        /** @var IEventDispatcher|\PHPUnit\Framework\MockObject\MockObject */
        private $eventDispatcher;
 
+       /** @var Session|MockObject */
+       private $userSession;
+
+       /** @var IUserManager|MockObject */
+       private $userManager;
+
+       /** @var IThrottler|MockObject */
+       private $throttler;
+
        /** @var AppPasswordController */
        private $controller;
 
@@ -73,6 +85,9 @@ class AppPasswordControllerTest extends TestCase {
                $this->credentialStore = $this->createMock(IStore::class);
                $this->request = $this->createMock(IRequest::class);
                $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
+               $this->userSession = $this->createMock(Session::class);
+               $this->userManager = $this->createMock(IUserManager::class);
+               $this->throttler = $this->createMock(IThrottler::class);
 
                $this->controller = new AppPasswordController(
                        'core',
@@ -81,7 +96,10 @@ class AppPasswordControllerTest extends TestCase {
                        $this->random,
                        $this->tokenProvider,
                        $this->credentialStore,
-                       $this->eventDispatcher
+                       $this->eventDispatcher,
+                       $this->userSession,
+                       $this->userManager,
+                       $this->throttler
                );
        }