aboutsummaryrefslogtreecommitdiffstats
path: root/apps/oauth2/lib
diff options
context:
space:
mode:
Diffstat (limited to 'apps/oauth2/lib')
-rw-r--r--apps/oauth2/lib/BackgroundJob/CleanupExpiredAuthorizationCode.php43
-rw-r--r--apps/oauth2/lib/Command/ImportLegacyOcClient.php76
-rw-r--r--apps/oauth2/lib/Controller/LoginRedirectorController.php125
-rw-r--r--apps/oauth2/lib/Controller/OauthApiController.php174
-rw-r--r--apps/oauth2/lib/Controller/SettingsController.php71
-rw-r--r--apps/oauth2/lib/Db/AccessToken.php40
-rw-r--r--apps/oauth2/lib/Db/AccessTokenMapper.php55
-rw-r--r--apps/oauth2/lib/Db/Client.php25
-rw-r--r--apps/oauth2/lib/Db/ClientMapper.php27
-rw-r--r--apps/oauth2/lib/Exceptions/AccessTokenNotFoundException.php22
-rw-r--r--apps/oauth2/lib/Exceptions/ClientNotFoundException.php22
-rw-r--r--apps/oauth2/lib/Migration/SetTokenExpiration.php46
-rw-r--r--apps/oauth2/lib/Migration/Version010401Date20181207190718.php21
-rw-r--r--apps/oauth2/lib/Migration/Version010402Date20190107124745.php21
-rw-r--r--apps/oauth2/lib/Migration/Version011601Date20230522143227.php65
-rw-r--r--apps/oauth2/lib/Migration/Version011602Date20230613160650.php39
-rw-r--r--apps/oauth2/lib/Migration/Version011603Date20230620111039.php69
-rw-r--r--apps/oauth2/lib/Migration/Version011901Date20240829164356.php49
-rw-r--r--apps/oauth2/lib/Settings/Admin.php56
19 files changed, 631 insertions, 415 deletions
diff --git a/apps/oauth2/lib/BackgroundJob/CleanupExpiredAuthorizationCode.php b/apps/oauth2/lib/BackgroundJob/CleanupExpiredAuthorizationCode.php
new file mode 100644
index 00000000000..b819a45ace2
--- /dev/null
+++ b/apps/oauth2/lib/BackgroundJob/CleanupExpiredAuthorizationCode.php
@@ -0,0 +1,43 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+
+namespace OCA\OAuth2\BackgroundJob;
+
+use OCA\OAuth2\Db\AccessTokenMapper;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\TimedJob;
+use OCP\DB\Exception;
+use Psr\Log\LoggerInterface;
+
+class CleanupExpiredAuthorizationCode extends TimedJob {
+
+ public function __construct(
+ ITimeFactory $timeFactory,
+ private AccessTokenMapper $accessTokenMapper,
+ private LoggerInterface $logger,
+ ) {
+ parent::__construct($timeFactory);
+ // 30 days
+ $this->setInterval(60 * 60 * 24 * 30);
+ $this->setTimeSensitivity(self::TIME_INSENSITIVE);
+ }
+
+ /**
+ * @param mixed $argument
+ * @inheritDoc
+ */
+ protected function run($argument): void {
+ try {
+ $this->accessTokenMapper->cleanupExpiredAuthorizationCode();
+ } catch (Exception $e) {
+ $this->logger->warning('Failed to cleanup tokens with expired authorization code', ['exception' => $e]);
+ }
+ }
+}
diff --git a/apps/oauth2/lib/Command/ImportLegacyOcClient.php b/apps/oauth2/lib/Command/ImportLegacyOcClient.php
new file mode 100644
index 00000000000..acdc57cf991
--- /dev/null
+++ b/apps/oauth2/lib/Command/ImportLegacyOcClient.php
@@ -0,0 +1,76 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCA\OAuth2\Command;
+
+use OCA\OAuth2\Db\Client;
+use OCA\OAuth2\Db\ClientMapper;
+use OCP\IConfig;
+use OCP\Security\ICrypto;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class ImportLegacyOcClient extends Command {
+ private const ARGUMENT_CLIENT_ID = 'client-id';
+ private const ARGUMENT_CLIENT_SECRET = 'client-secret';
+
+ public function __construct(
+ private readonly IConfig $config,
+ private readonly ICrypto $crypto,
+ private readonly ClientMapper $clientMapper,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this->setName('oauth2:import-legacy-oc-client');
+ $this->setDescription('This command is only required to be run on instances which were migrated from ownCloud without the oauth2.enable_oc_clients system config! Import a legacy Oauth2 client from an ownCloud instance and migrate it. The data is expected to be straight out of the database table oc_oauth2_clients.');
+ $this->addArgument(
+ self::ARGUMENT_CLIENT_ID,
+ InputArgument::REQUIRED,
+ 'Value of the "identifier" column',
+ );
+ $this->addArgument(
+ self::ARGUMENT_CLIENT_SECRET,
+ InputArgument::REQUIRED,
+ 'Value of the "secret" column',
+ );
+ }
+
+ public function isEnabled(): bool {
+ return $this->config->getSystemValueBool('oauth2.enable_oc_clients', false);
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ /** @var string $clientId */
+ $clientId = $input->getArgument(self::ARGUMENT_CLIENT_ID);
+
+ /** @var string $clientSecret */
+ $clientSecret = $input->getArgument(self::ARGUMENT_CLIENT_SECRET);
+
+ // Should not happen but just to be sure
+ if (empty($clientId) || empty($clientSecret)) {
+ return 1;
+ }
+
+ $hashedClientSecret = bin2hex($this->crypto->calculateHMAC($clientSecret));
+
+ $client = new Client();
+ $client->setName('ownCloud Desktop Client');
+ $client->setRedirectUri('http://localhost:*');
+ $client->setClientIdentifier($clientId);
+ $client->setSecret($hashedClientSecret);
+ $this->clientMapper->insert($client);
+
+ $output->writeln('<info>Client imported successfully</info>');
+ return 0;
+ }
+}
diff --git a/apps/oauth2/lib/Controller/LoginRedirectorController.php b/apps/oauth2/lib/Controller/LoginRedirectorController.php
index 57f18a97f85..7241b35cdcf 100644
--- a/apps/oauth2/lib/Controller/LoginRedirectorController.php
+++ b/apps/oauth2/lib/Controller/LoginRedirectorController.php
@@ -3,51 +3,32 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
- *
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @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 OCA\OAuth2\Controller;
+use OC\Core\Controller\ClientFlowLoginController;
use OCA\OAuth2\Db\ClientMapper;
use OCA\OAuth2\Exceptions\ClientNotFoundException;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http;
+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\RedirectResponse;
-use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TemplateResponse;
+use OCP\IAppConfig;
+use OCP\IConfig;
use OCP\IL10N;
use OCP\IRequest;
use OCP\ISession;
use OCP\IURLGenerator;
+use OCP\Security\ISecureRandom;
+#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
class LoginRedirectorController extends Controller {
- /** @var IURLGenerator */
- private $urlGenerator;
- /** @var ClientMapper */
- private $clientMapper;
- /** @var ISession */
- private $session;
- /** @var IL10N */
- private $l;
-
/**
* @param string $appName
* @param IRequest $request
@@ -56,32 +37,39 @@ class LoginRedirectorController extends Controller {
* @param ISession $session
* @param IL10N $l
*/
- public function __construct(string $appName,
- IRequest $request,
- IURLGenerator $urlGenerator,
- ClientMapper $clientMapper,
- ISession $session,
- IL10N $l) {
+ public function __construct(
+ string $appName,
+ IRequest $request,
+ private IURLGenerator $urlGenerator,
+ private ClientMapper $clientMapper,
+ private ISession $session,
+ private IL10N $l,
+ private ISecureRandom $random,
+ private IAppConfig $appConfig,
+ private IConfig $config,
+ ) {
parent::__construct($appName, $request);
- $this->urlGenerator = $urlGenerator;
- $this->clientMapper = $clientMapper;
- $this->session = $session;
- $this->l = $l;
}
/**
- * @PublicPage
- * @NoCSRFRequired
- * @UseSession
+ * Authorize the user
+ *
+ * @param string $client_id Client ID
+ * @param string $state State of the flow
+ * @param string $response_type Response type for the flow
+ * @param string $redirect_uri URI to redirect to after the flow (is only used for legacy ownCloud clients)
+ * @return TemplateResponse<Http::STATUS_OK, array{}>|RedirectResponse<Http::STATUS_SEE_OTHER, array{}>
*
- * @param string $client_id
- * @param string $state
- * @param string $response_type
- * @return Response
+ * 200: Client not found
+ * 303: Redirect to login URL
*/
+ #[PublicPage]
+ #[NoCSRFRequired]
+ #[UseSession]
public function authorize($client_id,
- $state,
- $response_type): Response {
+ $state,
+ $response_type,
+ string $redirect_uri = ''): TemplateResponse|RedirectResponse {
try {
$client = $this->clientMapper->getByIdentifier($client_id);
} catch (ClientNotFoundException $e) {
@@ -97,14 +85,39 @@ class LoginRedirectorController extends Controller {
return new RedirectResponse($url);
}
+ $enableOcClients = $this->config->getSystemValueBool('oauth2.enable_oc_clients', false);
+
+ $providedRedirectUri = '';
+ if ($enableOcClients && $client->getRedirectUri() === 'http://localhost:*') {
+ $providedRedirectUri = $redirect_uri;
+ }
+
$this->session->set('oauth.state', $state);
- $targetUrl = $this->urlGenerator->linkToRouteAbsolute(
- 'core.ClientFlowLogin.showAuthPickerPage',
- [
- 'clientIdentifier' => $client->getClientIdentifier(),
- ]
- );
+ if (in_array($client->getName(), $this->appConfig->getValueArray('oauth2', 'skipAuthPickerApplications', []))) {
+ /** @see ClientFlowLoginController::showAuthPickerPage **/
+ $stateToken = $this->random->generate(
+ 64,
+ ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_DIGITS
+ );
+ $this->session->set(ClientFlowLoginController::STATE_NAME, $stateToken);
+ $targetUrl = $this->urlGenerator->linkToRouteAbsolute(
+ 'core.ClientFlowLogin.grantPage',
+ [
+ 'stateToken' => $stateToken,
+ 'clientIdentifier' => $client->getClientIdentifier(),
+ 'providedRedirectUri' => $providedRedirectUri,
+ ]
+ );
+ } else {
+ $targetUrl = $this->urlGenerator->linkToRouteAbsolute(
+ 'core.ClientFlowLogin.showAuthPickerPage',
+ [
+ 'clientIdentifier' => $client->getClientIdentifier(),
+ 'providedRedirectUri' => $providedRedirectUri,
+ ]
+ );
+ }
return new RedirectResponse($targetUrl);
}
}
diff --git a/apps/oauth2/lib/Controller/OauthApiController.php b/apps/oauth2/lib/Controller/OauthApiController.php
index 910fdc99432..11f17fda4bf 100644
--- a/apps/oauth2/lib/Controller/OauthApiController.php
+++ b/apps/oauth2/lib/Controller/OauthApiController.php
@@ -3,99 +3,83 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @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 OCA\OAuth2\Controller;
-use OC\Authentication\Exceptions\ExpiredTokenException;
-use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Token\IProvider as TokenProvider;
-use OC\Security\Bruteforce\Throttler;
use OCA\OAuth2\Db\AccessTokenMapper;
use OCA\OAuth2\Db\ClientMapper;
use OCA\OAuth2\Exceptions\AccessTokenNotFoundException;
use OCA\OAuth2\Exceptions\ClientNotFoundException;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\BruteForceProtection;
+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\Utility\ITimeFactory;
+use OCP\Authentication\Exceptions\ExpiredTokenException;
+use OCP\Authentication\Exceptions\InvalidTokenException;
+use OCP\DB\Exception;
use OCP\IRequest;
+use OCP\Security\Bruteforce\IThrottler;
use OCP\Security\ICrypto;
use OCP\Security\ISecureRandom;
+use Psr\Log\LoggerInterface;
+#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
class OauthApiController extends Controller {
- /** @var AccessTokenMapper */
- private $accessTokenMapper;
- /** @var ClientMapper */
- private $clientMapper;
- /** @var ICrypto */
- private $crypto;
- /** @var TokenProvider */
- private $tokenProvider;
- /** @var ISecureRandom */
- private $secureRandom;
- /** @var ITimeFactory */
- private $time;
- /** @var Throttler */
- private $throttler;
-
- public function __construct(string $appName,
- IRequest $request,
- ICrypto $crypto,
- AccessTokenMapper $accessTokenMapper,
- ClientMapper $clientMapper,
- TokenProvider $tokenProvider,
- ISecureRandom $secureRandom,
- ITimeFactory $time,
- Throttler $throttler) {
+ // the authorization code expires after 10 minutes
+ public const AUTHORIZATION_CODE_EXPIRES_AFTER = 10 * 60;
+
+ public function __construct(
+ string $appName,
+ IRequest $request,
+ private ICrypto $crypto,
+ private AccessTokenMapper $accessTokenMapper,
+ private ClientMapper $clientMapper,
+ private TokenProvider $tokenProvider,
+ private ISecureRandom $secureRandom,
+ private ITimeFactory $time,
+ private LoggerInterface $logger,
+ private IThrottler $throttler,
+ private ITimeFactory $timeFactory,
+ ) {
parent::__construct($appName, $request);
- $this->crypto = $crypto;
- $this->accessTokenMapper = $accessTokenMapper;
- $this->clientMapper = $clientMapper;
- $this->tokenProvider = $tokenProvider;
- $this->secureRandom = $secureRandom;
- $this->time = $time;
- $this->throttler = $throttler;
}
/**
- * @PublicPage
- * @NoCSRFRequired
+ * Get a token
*
- * @param string $grant_type
- * @param string $code
- * @param string $refresh_token
- * @param string $client_id
- * @param string $client_secret
- * @return JSONResponse
+ * @param string $grant_type Token type that should be granted
+ * @param ?string $code Code of the flow
+ * @param ?string $refresh_token Refresh token
+ * @param ?string $client_id Client ID
+ * @param ?string $client_secret Client secret
+ * @throws Exception
+ * @return JSONResponse<Http::STATUS_OK, array{access_token: string, token_type: string, expires_in: int, refresh_token: string, user_id: string}, array{}>|JSONResponse<Http::STATUS_BAD_REQUEST, array{error: string}, array{}>
+ *
+ * 200: Token returned
+ * 400: Getting token is not possible
*/
- public function getToken($grant_type, $code, $refresh_token, $client_id, $client_secret): JSONResponse {
+ #[PublicPage]
+ #[NoCSRFRequired]
+ #[BruteForceProtection(action: 'oauth2GetToken')]
+ public function getToken(
+ string $grant_type, ?string $code, ?string $refresh_token,
+ ?string $client_id, ?string $client_secret,
+ ): JSONResponse {
// We only handle two types
if ($grant_type !== 'authorization_code' && $grant_type !== 'refresh_token') {
- return new JSONResponse([
+ $response = new JSONResponse([
'error' => 'invalid_grant',
], Http::STATUS_BAD_REQUEST);
+ $response->throttle(['invalid_grant' => $grant_type]);
+ return $response;
}
// We handle the initial and refresh tokens the same way
@@ -106,17 +90,48 @@ class OauthApiController extends Controller {
try {
$accessToken = $this->accessTokenMapper->getByCode($code);
} catch (AccessTokenNotFoundException $e) {
- return new JSONResponse([
+ $response = new JSONResponse([
'error' => 'invalid_request',
], Http::STATUS_BAD_REQUEST);
+ $response->throttle(['invalid_request' => 'token not found', 'code' => $code]);
+ return $response;
+ }
+
+ if ($grant_type === 'authorization_code') {
+ // check this token is in authorization code state
+ $deliveredTokenCount = $accessToken->getTokenCount();
+ if ($deliveredTokenCount > 0) {
+ $response = new JSONResponse([
+ 'error' => 'invalid_request',
+ ], Http::STATUS_BAD_REQUEST);
+ $response->throttle(['invalid_request' => 'authorization_code_received_for_active_token']);
+ return $response;
+ }
+
+ // check authorization code expiration
+ $now = $this->timeFactory->now()->getTimestamp();
+ $codeCreatedAt = $accessToken->getCodeCreatedAt();
+ if ($codeCreatedAt < $now - self::AUTHORIZATION_CODE_EXPIRES_AFTER) {
+ // we know this token is not useful anymore
+ $this->accessTokenMapper->delete($accessToken);
+
+ $response = new JSONResponse([
+ 'error' => 'invalid_request',
+ ], Http::STATUS_BAD_REQUEST);
+ $expiredSince = $now - self::AUTHORIZATION_CODE_EXPIRES_AFTER - $codeCreatedAt;
+ $response->throttle(['invalid_request' => 'authorization_code_expired', 'expired_since' => $expiredSince]);
+ return $response;
+ }
}
try {
$client = $this->clientMapper->getByUid($accessToken->getClientId());
} catch (ClientNotFoundException $e) {
- return new JSONResponse([
+ $response = new JSONResponse([
'error' => 'invalid_request',
], Http::STATUS_BAD_REQUEST);
+ $response->throttle(['invalid_request' => 'client not found', 'client_id' => $accessToken->getClientId()]);
+ return $response;
}
if (isset($this->request->server['PHP_AUTH_USER'])) {
@@ -124,12 +139,24 @@ class OauthApiController extends Controller {
$client_secret = $this->request->server['PHP_AUTH_PW'];
}
- // The client id and secret must match. Else we don't provide an access token!
- if ($client->getClientIdentifier() !== $client_id || $client->getSecret() !== $client_secret) {
+ try {
+ $storedClientSecretHash = $client->getSecret();
+ $clientSecretHash = bin2hex($this->crypto->calculateHMAC($client_secret));
+ } catch (\Exception $e) {
+ $this->logger->error('OAuth client secret decryption error', ['exception' => $e]);
+ // we don't throttle here because it might not be a bruteforce attack
return new JSONResponse([
'error' => 'invalid_client',
], Http::STATUS_BAD_REQUEST);
}
+ // The client id and secret must match. Else we don't provide an access token!
+ if ($client->getClientIdentifier() !== $client_id || $storedClientSecretHash !== $clientSecretHash) {
+ $response = new JSONResponse([
+ 'error' => 'invalid_client',
+ ], Http::STATUS_BAD_REQUEST);
+ $response->throttle(['invalid_client' => 'client ID or secret does not match']);
+ return $response;
+ }
$decryptedToken = $this->crypto->decrypt($accessToken->getEncryptedToken(), $code);
@@ -141,9 +168,11 @@ class OauthApiController extends Controller {
} catch (InvalidTokenException $e) {
//We can't do anything...
$this->accessTokenMapper->delete($accessToken);
- return new JSONResponse([
+ $response = new JSONResponse([
'error' => 'invalid_request',
], Http::STATUS_BAD_REQUEST);
+ $response->throttle(['invalid_request' => 'token is invalid']);
+ return $response;
}
// Rotate the apptoken (so the old one becomes invalid basically)
@@ -163,6 +192,11 @@ class OauthApiController extends Controller {
$newCode = $this->secureRandom->generate(128, ISecureRandom::CHAR_ALPHANUMERIC);
$accessToken->setHashedCode(hash('sha512', $newCode));
$accessToken->setEncryptedToken($this->crypto->encrypt($newToken, $newCode));
+ // increase the number of delivered oauth token
+ // this helps with cleaning up DB access token when authorization code has expired
+ // and it never delivered any oauth token
+ $tokenCount = $accessToken->getTokenCount();
+ $accessToken->setTokenCount($tokenCount + 1);
$this->accessTokenMapper->update($accessToken);
$this->throttler->resetDelay($this->request->getRemoteAddress(), 'login', ['user' => $appToken->getUID()]);
diff --git a/apps/oauth2/lib/Controller/SettingsController.php b/apps/oauth2/lib/Controller/SettingsController.php
index 046e6d77041..9bd02c8a2cd 100644
--- a/apps/oauth2/lib/Controller/SettingsController.php
+++ b/apps/oauth2/lib/Controller/SettingsController.php
@@ -3,30 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
- *
- * @author Bjoern Schiessle <bjoern@schiessle.org>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Patrik Kernstock <info@pkern.at>
- * @author rakekniven <mark.ziegler@rakekniven.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: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\OAuth2\Controller;
@@ -36,38 +14,34 @@ use OCA\OAuth2\Db\ClientMapper;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
+use OCP\Authentication\Token\IProvider as IAuthTokenProvider;
use OCP\IL10N;
use OCP\IRequest;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\Security\ICrypto;
use OCP\Security\ISecureRandom;
class SettingsController extends Controller {
- /** @var ClientMapper */
- private $clientMapper;
- /** @var ISecureRandom */
- private $secureRandom;
- /** @var AccessTokenMapper */
- private $accessTokenMapper;
- /** @var IL10N */
- private $l;
public const validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
- public function __construct(string $appName,
- IRequest $request,
- ClientMapper $clientMapper,
- ISecureRandom $secureRandom,
- AccessTokenMapper $accessTokenMapper,
- IL10N $l
+ public function __construct(
+ string $appName,
+ IRequest $request,
+ private ClientMapper $clientMapper,
+ private ISecureRandom $secureRandom,
+ private AccessTokenMapper $accessTokenMapper,
+ private IL10N $l,
+ private IAuthTokenProvider $tokenProvider,
+ private IUserManager $userManager,
+ private ICrypto $crypto,
) {
parent::__construct($appName, $request);
- $this->secureRandom = $secureRandom;
- $this->clientMapper = $clientMapper;
- $this->accessTokenMapper = $accessTokenMapper;
- $this->l = $l;
}
public function addClient(string $name,
- string $redirectUri): JSONResponse {
+ string $redirectUri): JSONResponse {
if (filter_var($redirectUri, FILTER_VALIDATE_URL) === false) {
return new JSONResponse(['message' => $this->l->t('Your redirect URL needs to be a full URL for example: https://yourdomain.com/path')], Http::STATUS_BAD_REQUEST);
}
@@ -75,7 +49,9 @@ class SettingsController extends Controller {
$client = new Client();
$client->setName($name);
$client->setRedirectUri($redirectUri);
- $client->setSecret($this->secureRandom->generate(64, self::validChars));
+ $secret = $this->secureRandom->generate(64, self::validChars);
+ $hashedSecret = bin2hex($this->crypto->calculateHMAC($secret));
+ $client->setSecret($hashedSecret);
$client->setClientIdentifier($this->secureRandom->generate(64, self::validChars));
$client = $this->clientMapper->insert($client);
@@ -84,7 +60,7 @@ class SettingsController extends Controller {
'name' => $client->getName(),
'redirectUri' => $client->getRedirectUri(),
'clientId' => $client->getClientIdentifier(),
- 'clientSecret' => $client->getSecret(),
+ 'clientSecret' => $secret,
];
return new JSONResponse($result);
@@ -92,6 +68,11 @@ class SettingsController extends Controller {
public function deleteClient(int $id): JSONResponse {
$client = $this->clientMapper->getByUid($id);
+
+ $this->userManager->callForSeenUsers(function (IUser $user) use ($client): void {
+ $this->tokenProvider->invalidateTokensOfUser($user->getUID(), $client->getName());
+ });
+
$this->accessTokenMapper->deleteByClientId($id);
$this->clientMapper->delete($client);
return new JSONResponse([]);
diff --git a/apps/oauth2/lib/Db/AccessToken.php b/apps/oauth2/lib/Db/AccessToken.php
index 26c830c64ad..34adc4f4797 100644
--- a/apps/oauth2/lib/Db/AccessToken.php
+++ b/apps/oauth2/lib/Db/AccessToken.php
@@ -1,29 +1,13 @@
<?php
+
/**
- * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
- *
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @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 OCA\OAuth2\Db;
use OCP\AppFramework\Db\Entity;
+use OCP\DB\Types;
/**
* @method int getTokenId()
@@ -34,6 +18,10 @@ use OCP\AppFramework\Db\Entity;
* @method void setEncryptedToken(string $token)
* @method string getHashedCode()
* @method void setHashedCode(string $token)
+ * @method int getCodeCreatedAt()
+ * @method void setCodeCreatedAt(int $createdAt)
+ * @method int getTokenCount()
+ * @method void setTokenCount(int $tokenCount)
*/
class AccessToken extends Entity {
/** @var int */
@@ -44,12 +32,18 @@ class AccessToken extends Entity {
protected $hashedCode;
/** @var string */
protected $encryptedToken;
+ /** @var int */
+ protected $codeCreatedAt;
+ /** @var int */
+ protected $tokenCount;
public function __construct() {
- $this->addType('id', 'int');
- $this->addType('tokenId', 'int');
- $this->addType('clientId', 'int');
+ $this->addType('id', Types::INTEGER);
+ $this->addType('tokenId', Types::INTEGER);
+ $this->addType('clientId', Types::INTEGER);
$this->addType('hashedCode', 'string');
$this->addType('encryptedToken', 'string');
+ $this->addType('codeCreatedAt', Types::INTEGER);
+ $this->addType('tokenCount', Types::INTEGER);
}
}
diff --git a/apps/oauth2/lib/Db/AccessTokenMapper.php b/apps/oauth2/lib/Db/AccessTokenMapper.php
index da351d5d496..8d5f6cf1da1 100644
--- a/apps/oauth2/lib/Db/AccessTokenMapper.php
+++ b/apps/oauth2/lib/Db/AccessTokenMapper.php
@@ -3,34 +3,17 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
- *
- * @author Bjoern Schiessle <bjoern@schiessle.org>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @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 OCA\OAuth2\Db;
+use OCA\OAuth2\Controller\OauthApiController;
use OCA\OAuth2\Exceptions\AccessTokenNotFoundException;
use OCP\AppFramework\Db\IMapperException;
use OCP\AppFramework\Db\QBMapper;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
@@ -39,10 +22,10 @@ use OCP\IDBConnection;
*/
class AccessTokenMapper extends QBMapper {
- /**
- * @param IDBConnection $db
- */
- public function __construct(IDBConnection $db) {
+ public function __construct(
+ IDBConnection $db,
+ private ITimeFactory $timeFactory,
+ ) {
parent::__construct($db, 'oauth2_access_tokens');
}
@@ -79,4 +62,24 @@ class AccessTokenMapper extends QBMapper {
->where($qb->expr()->eq('client_id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
$qb->executeStatement();
}
+
+ /**
+ * Delete access tokens that have an expired authorization code
+ * -> those that are old enough
+ * and which never delivered any oauth token (still in authorization state)
+ *
+ * @return void
+ * @throws Exception
+ */
+ public function cleanupExpiredAuthorizationCode(): void {
+ $now = $this->timeFactory->now()->getTimestamp();
+ $maxTokenCreationTs = $now - OauthApiController::AUTHORIZATION_CODE_EXPIRES_AFTER;
+
+ $qb = $this->db->getQueryBuilder();
+ $qb
+ ->delete($this->tableName)
+ ->where($qb->expr()->eq('token_count', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
+ ->andWhere($qb->expr()->lt('code_created_at', $qb->createNamedParameter($maxTokenCreationTs, IQueryBuilder::PARAM_INT)));
+ $qb->executeStatement();
+ }
}
diff --git a/apps/oauth2/lib/Db/Client.php b/apps/oauth2/lib/Db/Client.php
index 78f3d966928..8fce0040c96 100644
--- a/apps/oauth2/lib/Db/Client.php
+++ b/apps/oauth2/lib/Db/Client.php
@@ -1,28 +1,13 @@
<?php
+
/**
- * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
- *
- * @author Lukas Reschke <lukas@statuscode.ch>
- *
- * @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 OCA\OAuth2\Db;
use OCP\AppFramework\Db\Entity;
+use OCP\DB\Types;
/**
* @method string getClientIdentifier()
@@ -45,7 +30,7 @@ class Client extends Entity {
protected $secret;
public function __construct() {
- $this->addType('id', 'int');
+ $this->addType('id', Types::INTEGER);
$this->addType('name', 'string');
$this->addType('redirectUri', 'string');
$this->addType('clientIdentifier', 'string');
diff --git a/apps/oauth2/lib/Db/ClientMapper.php b/apps/oauth2/lib/Db/ClientMapper.php
index a3d5df35482..c5ca2989d0f 100644
--- a/apps/oauth2/lib/Db/ClientMapper.php
+++ b/apps/oauth2/lib/Db/ClientMapper.php
@@ -3,27 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
- *
- * @author Bjoern Schiessle <bjoern@schiessle.org>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @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 OCA\OAuth2\Db;
@@ -60,7 +41,7 @@ class ClientMapper extends QBMapper {
try {
$client = $this->findEntity($qb);
} catch (IMapperException $e) {
- throw new ClientNotFoundException('could not find client '.$clientIdentifier, 0, $e);
+ throw new ClientNotFoundException('could not find client ' . $clientIdentifier, 0, $e);
}
return $client;
}
@@ -80,7 +61,7 @@ class ClientMapper extends QBMapper {
try {
$client = $this->findEntity($qb);
} catch (IMapperException $e) {
- throw new ClientNotFoundException('could not find client with id '.$id, 0, $e);
+ throw new ClientNotFoundException('could not find client with id ' . $id, 0, $e);
}
return $client;
}
diff --git a/apps/oauth2/lib/Exceptions/AccessTokenNotFoundException.php b/apps/oauth2/lib/Exceptions/AccessTokenNotFoundException.php
index 8ccc32d67e1..809598e258e 100644
--- a/apps/oauth2/lib/Exceptions/AccessTokenNotFoundException.php
+++ b/apps/oauth2/lib/Exceptions/AccessTokenNotFoundException.php
@@ -3,26 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Lukas Reschke <lukas@statuscode.ch>
- *
- * @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 OCA\OAuth2\Exceptions;
diff --git a/apps/oauth2/lib/Exceptions/ClientNotFoundException.php b/apps/oauth2/lib/Exceptions/ClientNotFoundException.php
index d2f819449ce..cec7a24e22d 100644
--- a/apps/oauth2/lib/Exceptions/ClientNotFoundException.php
+++ b/apps/oauth2/lib/Exceptions/ClientNotFoundException.php
@@ -3,26 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Lukas Reschke <lukas@statuscode.ch>
- *
- * @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 OCA\OAuth2\Exceptions;
diff --git a/apps/oauth2/lib/Migration/SetTokenExpiration.php b/apps/oauth2/lib/Migration/SetTokenExpiration.php
index 7188985f3f5..dc925e26bb2 100644
--- a/apps/oauth2/lib/Migration/SetTokenExpiration.php
+++ b/apps/oauth2/lib/Migration/SetTokenExpiration.php
@@ -3,54 +3,26 @@
declare(strict_types=1);
/**
- * @copyright Copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @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 OCA\OAuth2\Migration;
-use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Token\IProvider as TokenProvider;
use OCA\OAuth2\Db\AccessToken;
use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Authentication\Exceptions\InvalidTokenException;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
class SetTokenExpiration implements IRepairStep {
- /** @var IDBConnection */
- private $connection;
-
- /** @var ITimeFactory */
- private $time;
-
- /** @var TokenProvider */
- private $tokenProvider;
-
- public function __construct(IDBConnection $connection,
- ITimeFactory $timeFactory,
- TokenProvider $tokenProvider) {
- $this->connection = $connection;
- $this->time = $timeFactory;
- $this->tokenProvider = $tokenProvider;
+ public function __construct(
+ private IDBConnection $connection,
+ private ITimeFactory $time,
+ private TokenProvider $tokenProvider,
+ ) {
}
public function getName(): string {
@@ -62,7 +34,7 @@ class SetTokenExpiration implements IRepairStep {
$qb->select('*')
->from('oauth2_access_tokens');
- $cursor = $qb->execute();
+ $cursor = $qb->executeQuery();
while ($row = $cursor->fetch()) {
$token = AccessToken::fromRow($row);
diff --git a/apps/oauth2/lib/Migration/Version010401Date20181207190718.php b/apps/oauth2/lib/Migration/Version010401Date20181207190718.php
index 51fd77c5528..8648826d53c 100644
--- a/apps/oauth2/lib/Migration/Version010401Date20181207190718.php
+++ b/apps/oauth2/lib/Migration/Version010401Date20181207190718.php
@@ -3,25 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright 2018, Roeland Jago Douma <roeland@famdouma.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/>.
- *
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\OAuth2\Migration;
diff --git a/apps/oauth2/lib/Migration/Version010402Date20190107124745.php b/apps/oauth2/lib/Migration/Version010402Date20190107124745.php
index b80e3a9575f..08099c625f7 100644
--- a/apps/oauth2/lib/Migration/Version010402Date20190107124745.php
+++ b/apps/oauth2/lib/Migration/Version010402Date20190107124745.php
@@ -3,25 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright 2018, Roeland Jago Douma <roeland@famdouma.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/>.
- *
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\OAuth2\Migration;
diff --git a/apps/oauth2/lib/Migration/Version011601Date20230522143227.php b/apps/oauth2/lib/Migration/Version011601Date20230522143227.php
new file mode 100644
index 00000000000..f2998202e02
--- /dev/null
+++ b/apps/oauth2/lib/Migration/Version011601Date20230522143227.php
@@ -0,0 +1,65 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\OAuth2\Migration;
+
+use Closure;
+use OCP\DB\ISchemaWrapper;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+use OCP\Security\ICrypto;
+
+class Version011601Date20230522143227 extends SimpleMigrationStep {
+
+ public function __construct(
+ private IDBConnection $connection,
+ private ICrypto $crypto,
+ ) {
+ }
+
+ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
+ /** @var ISchemaWrapper $schema */
+ $schema = $schemaClosure();
+
+ if ($schema->hasTable('oauth2_clients')) {
+ $table = $schema->getTable('oauth2_clients');
+ if ($table->hasColumn('secret')) {
+ $column = $table->getColumn('secret');
+ $column->setLength(512);
+ return $schema;
+ }
+ }
+
+ return null;
+ }
+
+ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) {
+ $qbUpdate = $this->connection->getQueryBuilder();
+ $qbUpdate->update('oauth2_clients')
+ ->set('secret', $qbUpdate->createParameter('updateSecret'))
+ ->where(
+ $qbUpdate->expr()->eq('id', $qbUpdate->createParameter('updateId'))
+ );
+
+ $qbSelect = $this->connection->getQueryBuilder();
+ $qbSelect->select('id', 'secret')
+ ->from('oauth2_clients');
+ $req = $qbSelect->executeQuery();
+ while ($row = $req->fetch()) {
+ $id = $row['id'];
+ $secret = $row['secret'];
+ $encryptedSecret = $this->crypto->encrypt($secret);
+ $qbUpdate->setParameter('updateSecret', $encryptedSecret, IQueryBuilder::PARAM_STR);
+ $qbUpdate->setParameter('updateId', $id, IQueryBuilder::PARAM_INT);
+ $qbUpdate->executeStatement();
+ }
+ $req->closeCursor();
+ }
+}
diff --git a/apps/oauth2/lib/Migration/Version011602Date20230613160650.php b/apps/oauth2/lib/Migration/Version011602Date20230613160650.php
new file mode 100644
index 00000000000..06efce324b2
--- /dev/null
+++ b/apps/oauth2/lib/Migration/Version011602Date20230613160650.php
@@ -0,0 +1,39 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\OAuth2\Migration;
+
+use Closure;
+use OCP\DB\ISchemaWrapper;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+class Version011602Date20230613160650 extends SimpleMigrationStep {
+
+ public function __construct(
+ ) {
+ }
+
+ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
+ /** @var ISchemaWrapper $schema */
+ $schema = $schemaClosure();
+
+ if ($schema->hasTable('oauth2_clients')) {
+ $table = $schema->getTable('oauth2_clients');
+ if ($table->hasColumn('secret')) {
+ $column = $table->getColumn('secret');
+ // we still change the column length in case Version011601Date20230522143227
+ // has run before it was changed to set the length to 512
+ $column->setLength(512);
+ return $schema;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/apps/oauth2/lib/Migration/Version011603Date20230620111039.php b/apps/oauth2/lib/Migration/Version011603Date20230620111039.php
new file mode 100644
index 00000000000..853eacd2873
--- /dev/null
+++ b/apps/oauth2/lib/Migration/Version011603Date20230620111039.php
@@ -0,0 +1,69 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\OAuth2\Migration;
+
+use Closure;
+use OCP\DB\ISchemaWrapper;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\DB\Types;
+use OCP\IDBConnection;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+class Version011603Date20230620111039 extends SimpleMigrationStep {
+
+ public function __construct(
+ private IDBConnection $connection,
+ ) {
+ }
+
+ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
+ /** @var ISchemaWrapper $schema */
+ $schema = $schemaClosure();
+
+ if ($schema->hasTable('oauth2_access_tokens')) {
+ $table = $schema->getTable('oauth2_access_tokens');
+ $dbChanged = false;
+ if (!$table->hasColumn('code_created_at')) {
+ $table->addColumn('code_created_at', Types::BIGINT, [
+ 'notnull' => true,
+ 'default' => 0,
+ 'unsigned' => true,
+ ]);
+ $dbChanged = true;
+ }
+ if (!$table->hasColumn('token_count')) {
+ $table->addColumn('token_count', Types::BIGINT, [
+ 'notnull' => true,
+ 'default' => 0,
+ 'unsigned' => true,
+ ]);
+ $dbChanged = true;
+ }
+ if (!$table->hasIndex('oauth2_tk_c_created_idx')) {
+ $table->addIndex(['token_count', 'code_created_at'], 'oauth2_tk_c_created_idx');
+ $dbChanged = true;
+ }
+ if ($dbChanged) {
+ return $schema;
+ }
+ }
+
+ return null;
+ }
+
+ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
+ // we consider that existing access_tokens have already produced at least one oauth token
+ // which prevents cleaning them up
+ $qbUpdate = $this->connection->getQueryBuilder();
+ $qbUpdate->update('oauth2_access_tokens')
+ ->set('token_count', $qbUpdate->createNamedParameter(1, IQueryBuilder::PARAM_INT));
+ $qbUpdate->executeStatement();
+ }
+}
diff --git a/apps/oauth2/lib/Migration/Version011901Date20240829164356.php b/apps/oauth2/lib/Migration/Version011901Date20240829164356.php
new file mode 100644
index 00000000000..20f5754bf11
--- /dev/null
+++ b/apps/oauth2/lib/Migration/Version011901Date20240829164356.php
@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\OAuth2\Migration;
+
+use Closure;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+use OCP\Security\ICrypto;
+
+class Version011901Date20240829164356 extends SimpleMigrationStep {
+
+ public function __construct(
+ private IDBConnection $connection,
+ private ICrypto $crypto,
+ ) {
+ }
+
+ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
+ $qbUpdate = $this->connection->getQueryBuilder();
+ $qbUpdate->update('oauth2_clients')
+ ->set('secret', $qbUpdate->createParameter('updateSecret'))
+ ->where(
+ $qbUpdate->expr()->eq('id', $qbUpdate->createParameter('updateId'))
+ );
+
+ $qbSelect = $this->connection->getQueryBuilder();
+ $qbSelect->select('id', 'secret')
+ ->from('oauth2_clients');
+ $req = $qbSelect->executeQuery();
+ while ($row = $req->fetch()) {
+ $id = $row['id'];
+ $storedEncryptedSecret = $row['secret'];
+ $secret = $this->crypto->decrypt($storedEncryptedSecret);
+ $hashedSecret = bin2hex($this->crypto->calculateHMAC($secret));
+ $qbUpdate->setParameter('updateSecret', $hashedSecret, IQueryBuilder::PARAM_STR);
+ $qbUpdate->setParameter('updateId', $id, IQueryBuilder::PARAM_INT);
+ $qbUpdate->executeStatement();
+ }
+ $req->closeCursor();
+ }
+}
diff --git a/apps/oauth2/lib/Settings/Admin.php b/apps/oauth2/lib/Settings/Admin.php
index aa2bd6db012..93b6b7bcc3f 100644
--- a/apps/oauth2/lib/Settings/Admin.php
+++ b/apps/oauth2/lib/Settings/Admin.php
@@ -3,48 +3,26 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
- *
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @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 OCA\OAuth2\Settings;
use OCA\OAuth2\Db\ClientMapper;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
-use OCP\Settings\ISettings;
use OCP\IURLGenerator;
+use OCP\Settings\ISettings;
+use Psr\Log\LoggerInterface;
class Admin implements ISettings {
- private IInitialState $initialState;
- private ClientMapper $clientMapper;
- private IURLGenerator $urlGenerator;
public function __construct(
- IInitialState $initialState,
- ClientMapper $clientMapper,
- IURLGenerator $urlGenerator
+ private IInitialState $initialState,
+ private ClientMapper $clientMapper,
+ private IURLGenerator $urlGenerator,
+ private LoggerInterface $logger,
) {
- $this->initialState = $initialState;
- $this->clientMapper = $clientMapper;
- $this->urlGenerator = $urlGenerator;
}
public function getForm(): TemplateResponse {
@@ -52,13 +30,17 @@ class Admin implements ISettings {
$result = [];
foreach ($clients as $client) {
- $result[] = [
- 'id' => $client->getId(),
- 'name' => $client->getName(),
- 'redirectUri' => $client->getRedirectUri(),
- 'clientId' => $client->getClientIdentifier(),
- 'clientSecret' => $client->getSecret(),
- ];
+ try {
+ $result[] = [
+ 'id' => $client->getId(),
+ 'name' => $client->getName(),
+ 'redirectUri' => $client->getRedirectUri(),
+ 'clientId' => $client->getClientIdentifier(),
+ 'clientSecret' => '',
+ ];
+ } catch (\Exception $e) {
+ $this->logger->error('[Settings] OAuth client secret decryption error', ['exception' => $e]);
+ }
}
$this->initialState->provideInitialState('clients', $result);
$this->initialState->provideInitialState('oauth2-doc-link', $this->urlGenerator->linkToDocs('admin-oauth2'));