AppPasswordController.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * @copyright Copyright (c) 2018, Roeland Jago Douma <roeland@famdouma.nl>
  5. *
  6. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  7. * @author Daniel Kesselberg <mail@danielkesselberg.de>
  8. * @author Roeland Jago Douma <roeland@famdouma.nl>
  9. * @author Kate Döen <kate.doeen@nextcloud.com>
  10. *
  11. * @license GNU AGPL version 3 or any later version
  12. *
  13. * This program is free software: you can redistribute it and/or modify
  14. * it under the terms of the GNU Affero General Public License as
  15. * published by the Free Software Foundation, either version 3 of the
  16. * License, or (at your option) any later version.
  17. *
  18. * This program is distributed in the hope that it will be useful,
  19. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. * GNU Affero General Public License for more details.
  22. *
  23. * You should have received a copy of the GNU Affero General Public License
  24. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  25. *
  26. */
  27. namespace OC\Core\Controller;
  28. use OC\Authentication\Events\AppPasswordCreatedEvent;
  29. use OC\Authentication\Token\IProvider;
  30. use OC\Authentication\Token\IToken;
  31. use OC\User\Session;
  32. use OCP\AppFramework\Http;
  33. use OCP\AppFramework\Http\Attribute\ApiRoute;
  34. use OCP\AppFramework\Http\Attribute\UseSession;
  35. use OCP\AppFramework\Http\DataResponse;
  36. use OCP\AppFramework\OCS\OCSForbiddenException;
  37. use OCP\Authentication\Exceptions\CredentialsUnavailableException;
  38. use OCP\Authentication\Exceptions\InvalidTokenException;
  39. use OCP\Authentication\Exceptions\PasswordUnavailableException;
  40. use OCP\Authentication\LoginCredentials\IStore;
  41. use OCP\EventDispatcher\IEventDispatcher;
  42. use OCP\IRequest;
  43. use OCP\ISession;
  44. use OCP\IUserManager;
  45. use OCP\Security\Bruteforce\IThrottler;
  46. use OCP\Security\ISecureRandom;
  47. class AppPasswordController extends \OCP\AppFramework\OCSController {
  48. public function __construct(
  49. string $appName,
  50. IRequest $request,
  51. private ISession $session,
  52. private ISecureRandom $random,
  53. private IProvider $tokenProvider,
  54. private IStore $credentialStore,
  55. private IEventDispatcher $eventDispatcher,
  56. private Session $userSession,
  57. private IUserManager $userManager,
  58. private IThrottler $throttler,
  59. ) {
  60. parent::__construct($appName, $request);
  61. }
  62. /**
  63. * @NoAdminRequired
  64. * @PasswordConfirmationRequired
  65. *
  66. * Create app password
  67. *
  68. * @return DataResponse<Http::STATUS_OK, array{apppassword: string}, array{}>
  69. * @throws OCSForbiddenException Creating app password is not allowed
  70. *
  71. * 200: App password returned
  72. */
  73. #[ApiRoute(verb: 'GET', url: '/getapppassword', root: '/core')]
  74. public function getAppPassword(): DataResponse {
  75. // We do not allow the creation of new tokens if this is an app password
  76. if ($this->session->exists('app_password')) {
  77. throw new OCSForbiddenException('You cannot request an new apppassword with an apppassword');
  78. }
  79. try {
  80. $credentials = $this->credentialStore->getLoginCredentials();
  81. } catch (CredentialsUnavailableException $e) {
  82. throw new OCSForbiddenException();
  83. }
  84. try {
  85. $password = $credentials->getPassword();
  86. } catch (PasswordUnavailableException $e) {
  87. $password = null;
  88. }
  89. $userAgent = $this->request->getHeader('USER_AGENT');
  90. $token = $this->random->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
  91. $generatedToken = $this->tokenProvider->generateToken(
  92. $token,
  93. $credentials->getUID(),
  94. $credentials->getLoginName(),
  95. $password,
  96. $userAgent,
  97. IToken::PERMANENT_TOKEN,
  98. IToken::DO_NOT_REMEMBER
  99. );
  100. $this->eventDispatcher->dispatchTyped(
  101. new AppPasswordCreatedEvent($generatedToken)
  102. );
  103. return new DataResponse([
  104. 'apppassword' => $token
  105. ]);
  106. }
  107. /**
  108. * @NoAdminRequired
  109. *
  110. * Delete app password
  111. *
  112. * @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
  113. * @throws OCSForbiddenException Deleting app password is not allowed
  114. *
  115. * 200: App password deleted successfully
  116. */
  117. #[ApiRoute(verb: 'DELETE', url: '/apppassword', root: '/core')]
  118. public function deleteAppPassword(): DataResponse {
  119. if (!$this->session->exists('app_password')) {
  120. throw new OCSForbiddenException('no app password in use');
  121. }
  122. $appPassword = $this->session->get('app_password');
  123. try {
  124. $token = $this->tokenProvider->getToken($appPassword);
  125. } catch (InvalidTokenException $e) {
  126. throw new OCSForbiddenException('could not remove apptoken');
  127. }
  128. $this->tokenProvider->invalidateTokenById($token->getUID(), $token->getId());
  129. return new DataResponse();
  130. }
  131. /**
  132. * @NoAdminRequired
  133. *
  134. * Rotate app password
  135. *
  136. * @return DataResponse<Http::STATUS_OK, array{apppassword: string}, array{}>
  137. * @throws OCSForbiddenException Rotating app password is not allowed
  138. *
  139. * 200: App password returned
  140. */
  141. #[ApiRoute(verb: 'POST', url: '/apppassword/rotate', root: '/core')]
  142. public function rotateAppPassword(): DataResponse {
  143. if (!$this->session->exists('app_password')) {
  144. throw new OCSForbiddenException('no app password in use');
  145. }
  146. $appPassword = $this->session->get('app_password');
  147. try {
  148. $token = $this->tokenProvider->getToken($appPassword);
  149. } catch (InvalidTokenException $e) {
  150. throw new OCSForbiddenException('could not rotate apptoken');
  151. }
  152. $newToken = $this->random->generate(72, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_DIGITS);
  153. $this->tokenProvider->rotate($token, $appPassword, $newToken);
  154. return new DataResponse([
  155. 'apppassword' => $newToken,
  156. ]);
  157. }
  158. /**
  159. * Confirm the user password
  160. *
  161. * @NoAdminRequired
  162. * @BruteForceProtection(action=sudo)
  163. *
  164. * @param string $password The password of the user
  165. *
  166. * @return DataResponse<Http::STATUS_OK, array{lastLogin: int}, array{}>|DataResponse<Http::STATUS_FORBIDDEN, array<empty>, array{}>
  167. *
  168. * 200: Password confirmation succeeded
  169. * 403: Password confirmation failed
  170. */
  171. #[UseSession]
  172. #[ApiRoute(verb: 'PUT', url: '/apppassword/confirm', root: '/core')]
  173. public function confirmUserPassword(string $password): DataResponse {
  174. $loginName = $this->userSession->getLoginName();
  175. $loginResult = $this->userManager->checkPassword($loginName, $password);
  176. if ($loginResult === false) {
  177. $response = new DataResponse([], Http::STATUS_FORBIDDEN);
  178. $response->throttle(['loginName' => $loginName]);
  179. return $response;
  180. }
  181. $confirmTimestamp = time();
  182. $this->session->set('last-password-confirm', $confirmTimestamp);
  183. $this->throttler->resetDelay($this->request->getRemoteAddress(), 'sudo', ['loginName' => $loginName]);
  184. return new DataResponse(['lastLogin' => $confirmTimestamp], Http::STATUS_OK);
  185. }
  186. }