mirror of
https://github.com/nextcloud/server.git
synced 2025-04-14 13:29:21 +00:00
fix(oauth2): retain support for legacy ownCloud clients
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
This commit is contained in:
parent
b03ffab5f0
commit
246da73a36
17 changed files with 264 additions and 13 deletions
apps
dav
appinfo/v1
lib
tests/unit/Connector/Sabre
oauth2
core
lib/private/Repair/Owncloud
openapi.jsontests/Core/Controller
|
@ -61,7 +61,8 @@ $authPlugin = new \Sabre\DAV\Auth\Plugin($authBackend);
|
|||
$bearerAuthPlugin = new BearerAuth(
|
||||
Server::get(IUserSession::class),
|
||||
Server::get(ISession::class),
|
||||
Server::get(IRequest::class)
|
||||
Server::get(IRequest::class),
|
||||
Server::get(IConfig::class),
|
||||
);
|
||||
$authPlugin->addBackend($bearerAuthPlugin);
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ namespace OCA\DAV\Connector\Sabre;
|
|||
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\Defaults;
|
||||
use OCP\IConfig;
|
||||
use OCP\IRequest;
|
||||
use OCP\ISession;
|
||||
use OCP\IUserSession;
|
||||
|
@ -19,6 +20,7 @@ class BearerAuth extends AbstractBearer {
|
|||
private IUserSession $userSession,
|
||||
private ISession $session,
|
||||
private IRequest $request,
|
||||
private IConfig $config,
|
||||
private string $principalPrefix = 'principals/users/',
|
||||
) {
|
||||
// setup realm
|
||||
|
@ -57,6 +59,14 @@ class BearerAuth extends AbstractBearer {
|
|||
* @param ResponseInterface $response
|
||||
*/
|
||||
public function challenge(RequestInterface $request, ResponseInterface $response): void {
|
||||
// Legacy ownCloud clients still authenticate via OAuth2
|
||||
$enableOcClients = $this->config->getSystemValueBool('oauth2.enable_oc_clients', false);
|
||||
$userAgent = $request->getHeader('User-Agent');
|
||||
if ($enableOcClients && $userAgent !== null && str_contains($userAgent, 'mirall')) {
|
||||
parent::challenge($request, $response);
|
||||
return;
|
||||
}
|
||||
|
||||
$response->setStatus(Http::STATUS_UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -157,7 +157,8 @@ class Server {
|
|||
$bearerAuthBackend = new BearerAuth(
|
||||
\OCP\Server::get(IUserSession::class),
|
||||
\OCP\Server::get(ISession::class),
|
||||
\OCP\Server::get(IRequest::class)
|
||||
\OCP\Server::get(IRequest::class),
|
||||
\OCP\Server::get(IConfig::class),
|
||||
);
|
||||
$authPlugin->addBackend($bearerAuthBackend);
|
||||
// because we are throwing exceptions this plugin has to be the last one
|
||||
|
|
|
@ -7,10 +7,12 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre;
|
|||
|
||||
use OC\User\Session;
|
||||
use OCA\DAV\Connector\Sabre\BearerAuth;
|
||||
use OCP\IConfig;
|
||||
use OCP\IRequest;
|
||||
use OCP\ISession;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserSession;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
use Test\TestCase;
|
||||
|
@ -28,17 +30,21 @@ class BearerAuthTest extends TestCase {
|
|||
/** @var BearerAuth */
|
||||
private $bearerAuth;
|
||||
|
||||
private IConfig&MockObject $config;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->userSession = $this->createMock(Session::class);
|
||||
$this->session = $this->createMock(ISession::class);
|
||||
$this->request = $this->createMock(IRequest::class);
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
|
||||
$this->bearerAuth = new BearerAuth(
|
||||
$this->userSession,
|
||||
$this->session,
|
||||
$this->request
|
||||
$this->request,
|
||||
$this->config,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,10 @@
|
|||
</post-migration>
|
||||
</repair-steps>
|
||||
|
||||
<commands>
|
||||
<command>OCA\OAuth2\Command\ImportLegacyOcClient</command>
|
||||
</commands>
|
||||
|
||||
<settings>
|
||||
<admin>OCA\OAuth2\Settings\Admin</admin>
|
||||
</settings>
|
||||
|
|
|
@ -8,6 +8,7 @@ $baseDir = $vendorDir;
|
|||
return array(
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
'OCA\\OAuth2\\BackgroundJob\\CleanupExpiredAuthorizationCode' => $baseDir . '/../lib/BackgroundJob/CleanupExpiredAuthorizationCode.php',
|
||||
'OCA\\OAuth2\\Command\\ImportLegacyOcClient' => $baseDir . '/../lib/Command/ImportLegacyOcClient.php',
|
||||
'OCA\\OAuth2\\Controller\\LoginRedirectorController' => $baseDir . '/../lib/Controller/LoginRedirectorController.php',
|
||||
'OCA\\OAuth2\\Controller\\OauthApiController' => $baseDir . '/../lib/Controller/OauthApiController.php',
|
||||
'OCA\\OAuth2\\Controller\\SettingsController' => $baseDir . '/../lib/Controller/SettingsController.php',
|
||||
|
|
|
@ -23,6 +23,7 @@ class ComposerStaticInitOAuth2
|
|||
public static $classMap = array (
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
'OCA\\OAuth2\\BackgroundJob\\CleanupExpiredAuthorizationCode' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupExpiredAuthorizationCode.php',
|
||||
'OCA\\OAuth2\\Command\\ImportLegacyOcClient' => __DIR__ . '/..' . '/../lib/Command/ImportLegacyOcClient.php',
|
||||
'OCA\\OAuth2\\Controller\\LoginRedirectorController' => __DIR__ . '/..' . '/../lib/Controller/LoginRedirectorController.php',
|
||||
'OCA\\OAuth2\\Controller\\OauthApiController' => __DIR__ . '/..' . '/../lib/Controller/OauthApiController.php',
|
||||
'OCA\\OAuth2\\Controller\\SettingsController' => __DIR__ . '/..' . '/../lib/Controller/SettingsController.php',
|
||||
|
|
76
apps/oauth2/lib/Command/ImportLegacyOcClient.php
Normal file
76
apps/oauth2/lib/Command/ImportLegacyOcClient.php
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ use OCP\AppFramework\Http\Attribute\UseSession;
|
|||
use OCP\AppFramework\Http\RedirectResponse;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
use OCP\ISession;
|
||||
|
@ -45,6 +46,7 @@ class LoginRedirectorController extends Controller {
|
|||
private IL10N $l,
|
||||
private ISecureRandom $random,
|
||||
private IAppConfig $appConfig,
|
||||
private IConfig $config,
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
}
|
||||
|
@ -55,6 +57,7 @@ class LoginRedirectorController extends Controller {
|
|||
* @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{}>
|
||||
*
|
||||
* 200: Client not found
|
||||
|
@ -65,7 +68,8 @@ class LoginRedirectorController extends Controller {
|
|||
#[UseSession]
|
||||
public function authorize($client_id,
|
||||
$state,
|
||||
$response_type): TemplateResponse|RedirectResponse {
|
||||
$response_type,
|
||||
string $redirect_uri = ''): TemplateResponse|RedirectResponse {
|
||||
try {
|
||||
$client = $this->clientMapper->getByIdentifier($client_id);
|
||||
} catch (ClientNotFoundException $e) {
|
||||
|
@ -81,6 +85,13 @@ 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);
|
||||
|
||||
if (in_array($client->getName(), $this->appConfig->getValueArray('oauth2', 'skipAuthPickerApplications', []))) {
|
||||
|
@ -95,6 +106,7 @@ class LoginRedirectorController extends Controller {
|
|||
[
|
||||
'stateToken' => $stateToken,
|
||||
'clientIdentifier' => $client->getClientIdentifier(),
|
||||
'providedRedirectUri' => $providedRedirectUri,
|
||||
]
|
||||
);
|
||||
} else {
|
||||
|
@ -102,6 +114,7 @@ class LoginRedirectorController extends Controller {
|
|||
'core.ClientFlowLogin.showAuthPickerPage',
|
||||
[
|
||||
'clientIdentifier' => $client->getClientIdentifier(),
|
||||
'providedRedirectUri' => $providedRedirectUri,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -65,6 +65,15 @@
|
|||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "redirect_uri",
|
||||
"in": "query",
|
||||
"description": "URI to redirect to after the flow (is only used for legacy ownCloud clients)",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
|
|
@ -13,6 +13,7 @@ use OCA\OAuth2\Exceptions\ClientNotFoundException;
|
|||
use OCP\AppFramework\Http\RedirectResponse;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
use OCP\ISession;
|
||||
|
@ -32,6 +33,7 @@ class LoginRedirectorControllerTest extends TestCase {
|
|||
private IL10N&MockObject $l;
|
||||
private ISecureRandom&MockObject $random;
|
||||
private IAppConfig&MockObject $appConfig;
|
||||
private IConfig&MockObject $config;
|
||||
|
||||
private LoginRedirectorController $loginRedirectorController;
|
||||
|
||||
|
@ -45,6 +47,7 @@ class LoginRedirectorControllerTest extends TestCase {
|
|||
$this->l = $this->createMock(IL10N::class);
|
||||
$this->random = $this->createMock(ISecureRandom::class);
|
||||
$this->appConfig = $this->createMock(IAppConfig::class);
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
|
||||
$this->loginRedirectorController = new LoginRedirectorController(
|
||||
'oauth2',
|
||||
|
@ -55,6 +58,7 @@ class LoginRedirectorControllerTest extends TestCase {
|
|||
$this->l,
|
||||
$this->random,
|
||||
$this->appConfig,
|
||||
$this->config,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -77,9 +81,15 @@ class LoginRedirectorControllerTest extends TestCase {
|
|||
'core.ClientFlowLogin.showAuthPickerPage',
|
||||
[
|
||||
'clientIdentifier' => 'MyClientIdentifier',
|
||||
'providedRedirectUri' => '',
|
||||
]
|
||||
)
|
||||
->willReturn('https://example.com/?clientIdentifier=foo');
|
||||
$this->config
|
||||
->expects($this->once())
|
||||
->method('getSystemValueBool')
|
||||
->with('oauth2.enable_oc_clients', false)
|
||||
->willReturn(false);
|
||||
|
||||
$expected = new RedirectResponse('https://example.com/?clientIdentifier=foo');
|
||||
$this->assertEquals($expected, $this->loginRedirectorController->authorize('MyClientId', 'MyState', 'code'));
|
||||
|
@ -124,9 +134,15 @@ class LoginRedirectorControllerTest extends TestCase {
|
|||
[
|
||||
'stateToken' => 'MyStateToken',
|
||||
'clientIdentifier' => 'MyClientIdentifier',
|
||||
'providedRedirectUri' => '',
|
||||
]
|
||||
)
|
||||
->willReturn('https://example.com/?clientIdentifier=foo');
|
||||
$this->config
|
||||
->expects($this->once())
|
||||
->method('getSystemValueBool')
|
||||
->with('oauth2.enable_oc_clients', false)
|
||||
->willReturn(false);
|
||||
|
||||
$expected = new RedirectResponse('https://example.com/?clientIdentifier=foo');
|
||||
$this->assertEquals($expected, $this->loginRedirectorController->authorize('MyClientId', 'MyState', 'code'));
|
||||
|
@ -150,6 +166,74 @@ class LoginRedirectorControllerTest extends TestCase {
|
|||
$this->assertEquals($expected, $this->loginRedirectorController->authorize('MyClientId', 'MyState', 'wrongcode'));
|
||||
}
|
||||
|
||||
public function testAuthorizeWithLegacyOcClient(): void {
|
||||
$client = new Client();
|
||||
$client->setClientIdentifier('MyClientIdentifier');
|
||||
$client->setRedirectUri('http://localhost:*');
|
||||
$this->clientMapper
|
||||
->expects($this->once())
|
||||
->method('getByIdentifier')
|
||||
->with('MyClientId')
|
||||
->willReturn($client);
|
||||
$this->session
|
||||
->expects($this->once())
|
||||
->method('set')
|
||||
->with('oauth.state', 'MyState');
|
||||
$this->urlGenerator
|
||||
->expects($this->once())
|
||||
->method('linkToRouteAbsolute')
|
||||
->with(
|
||||
'core.ClientFlowLogin.showAuthPickerPage',
|
||||
[
|
||||
'clientIdentifier' => 'MyClientIdentifier',
|
||||
'providedRedirectUri' => 'http://localhost:30000',
|
||||
]
|
||||
)
|
||||
->willReturn('https://example.com/?clientIdentifier=foo&providedRedirectUri=http://localhost:30000');
|
||||
$this->config
|
||||
->expects($this->once())
|
||||
->method('getSystemValueBool')
|
||||
->with('oauth2.enable_oc_clients', false)
|
||||
->willReturn(true);
|
||||
|
||||
$expected = new RedirectResponse('https://example.com/?clientIdentifier=foo&providedRedirectUri=http://localhost:30000');
|
||||
$this->assertEquals($expected, $this->loginRedirectorController->authorize('MyClientId', 'MyState', 'code', 'http://localhost:30000'));
|
||||
}
|
||||
|
||||
public function testAuthorizeNotForwardingUntrustedURIs(): void {
|
||||
$client = new Client();
|
||||
$client->setClientIdentifier('MyClientIdentifier');
|
||||
$this->clientMapper
|
||||
->expects($this->once())
|
||||
->method('getByIdentifier')
|
||||
->with('MyClientId')
|
||||
->willReturn($client);
|
||||
$this->session
|
||||
->expects($this->once())
|
||||
->method('set')
|
||||
->with('oauth.state', 'MyState');
|
||||
$this->urlGenerator
|
||||
->expects($this->once())
|
||||
->method('linkToRouteAbsolute')
|
||||
->with(
|
||||
'core.ClientFlowLogin.showAuthPickerPage',
|
||||
[
|
||||
'clientIdentifier' => 'MyClientIdentifier',
|
||||
'providedRedirectUri' => '',
|
||||
]
|
||||
)
|
||||
->willReturn('https://example.com/?clientIdentifier=foo');
|
||||
$this->config
|
||||
->expects($this->once())
|
||||
->method('getSystemValueBool')
|
||||
->with('oauth2.enable_oc_clients', false)
|
||||
->willReturn(false);
|
||||
|
||||
$expected = new RedirectResponse('https://example.com/?clientIdentifier=foo');
|
||||
$this->assertEquals($expected, $this->loginRedirectorController->authorize('MyClientId', 'MyState', 'code', 'http://untrusted-uri.com'));
|
||||
}
|
||||
|
||||
|
||||
public function testClientNotFound(): void {
|
||||
$clientNotFound = new ClientNotFoundException('could not find client test123', 0);
|
||||
$this->clientMapper
|
||||
|
|
|
@ -26,6 +26,7 @@ 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;
|
||||
|
@ -55,6 +56,7 @@ class ClientFlowLoginController extends Controller {
|
|||
private ICrypto $crypto,
|
||||
private IEventDispatcher $eventDispatcher,
|
||||
private ITimeFactory $timeFactory,
|
||||
private IConfig $config,
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
}
|
||||
|
@ -89,7 +91,7 @@ class ClientFlowLoginController extends Controller {
|
|||
#[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 !== '') {
|
||||
|
@ -142,6 +144,7 @@ class ClientFlowLoginController extends Controller {
|
|||
'oauthState' => $this->session->get('oauth.state'),
|
||||
'user' => $user,
|
||||
'direct' => $direct,
|
||||
'providedRedirectUri' => $providedRedirectUri,
|
||||
],
|
||||
'guest'
|
||||
);
|
||||
|
@ -161,6 +164,7 @@ class ClientFlowLoginController extends Controller {
|
|||
string $stateToken = '',
|
||||
string $clientIdentifier = '',
|
||||
int $direct = 0,
|
||||
string $providedRedirectUri = '',
|
||||
): Response {
|
||||
if (!$this->isValidToken($stateToken)) {
|
||||
return $this->stateTokenForbiddenResponse();
|
||||
|
@ -197,6 +201,7 @@ class ClientFlowLoginController extends Controller {
|
|||
'serverHost' => $this->getServerPath(),
|
||||
'oauthState' => $this->session->get('oauth.state'),
|
||||
'direct' => $direct,
|
||||
'providedRedirectUri' => $providedRedirectUri,
|
||||
],
|
||||
'guest'
|
||||
);
|
||||
|
@ -211,6 +216,7 @@ class ClientFlowLoginController extends Controller {
|
|||
public function generateAppPassword(
|
||||
string $stateToken,
|
||||
string $clientIdentifier = '',
|
||||
string $providedRedirectUri = '',
|
||||
): Response {
|
||||
if (!$this->isValidToken($stateToken)) {
|
||||
$this->session->remove(self::STATE_NAME);
|
||||
|
@ -270,7 +276,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 .= '&';
|
||||
|
|
|
@ -31,7 +31,7 @@ $urlGenerator = $_['urlGenerator'];
|
|||
<br/>
|
||||
|
||||
<p id="redirect-link">
|
||||
<form id="login-form" action="<?php p($urlGenerator->linkToRoute('core.ClientFlowLogin.grantPage', ['stateToken' => $_['stateToken'], 'clientIdentifier' => $_['clientIdentifier'], 'oauthState' => $_['oauthState'], 'user' => $_['user'], 'direct' => $_['direct']])) ?>" method="get">
|
||||
<form id="login-form" action="<?php p($urlGenerator->linkToRoute('core.ClientFlowLogin.grantPage', ['stateToken' => $_['stateToken'], 'clientIdentifier' => $_['clientIdentifier'], 'oauthState' => $_['oauthState'], 'user' => $_['user'], 'direct' => $_['direct'], 'providedRedirectUri' => $_['providedRedirectUri']])) ?>" method="get">
|
||||
<input type="submit" class="login primary icon-confirm-white" value="<?php p($l->t('Log in')) ?>" disabled>
|
||||
</form>
|
||||
</p>
|
||||
|
|
|
@ -35,6 +35,7 @@ $urlGenerator = $_['urlGenerator'];
|
|||
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']) ?>" />
|
||||
<input type="hidden" name="stateToken" value="<?php p($_['stateToken']) ?>" />
|
||||
<input type="hidden" name="oauthState" value="<?php p($_['oauthState']) ?>" />
|
||||
<input type="hidden" name="providedRedirectUri" value="<?php p($_['providedRedirectUri']) ?>">
|
||||
<?php if ($_['direct']) { ?>
|
||||
<input type="hidden" name="direct" value="1" />
|
||||
<?php } ?>
|
||||
|
|
|
@ -15,6 +15,7 @@ use OCA\OAuth2\Db\AccessTokenMapper;
|
|||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\Authentication\Token\IToken;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IConfig;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\IRepairStep;
|
||||
use OCP\Security\ICrypto;
|
||||
|
@ -29,6 +30,7 @@ class MigrateOauthTables implements IRepairStep {
|
|||
private ISecureRandom $random,
|
||||
private ITimeFactory $timeFactory,
|
||||
private ICrypto $crypto,
|
||||
private IConfig $config,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -169,7 +171,12 @@ class MigrateOauthTables implements IRepairStep {
|
|||
$schema = new SchemaWrapper($this->db);
|
||||
}
|
||||
|
||||
$output->info('Delete clients (and their related access tokens) with the redirect_uri starting with oc:// or ending with *');
|
||||
$enableOcClients = $this->config->getSystemValueBool('oauth2.enable_oc_clients', false);
|
||||
if ($enableOcClients) {
|
||||
$output->info('Delete clients (and their related access tokens) with the redirect_uri starting with oc://');
|
||||
} else {
|
||||
$output->info('Delete clients (and their related access tokens) with the redirect_uri starting with oc:// or ending with *');
|
||||
}
|
||||
// delete the access tokens
|
||||
$qbDeleteAccessTokens = $this->db->getQueryBuilder();
|
||||
|
||||
|
@ -178,10 +185,12 @@ class MigrateOauthTables implements IRepairStep {
|
|||
->from('oauth2_clients')
|
||||
->where(
|
||||
$qbSelectClientId->expr()->iLike('redirect_uri', $qbDeleteAccessTokens->createNamedParameter('oc://%', IQueryBuilder::PARAM_STR))
|
||||
)
|
||||
->orWhere(
|
||||
);
|
||||
if (!$enableOcClients) {
|
||||
$qbSelectClientId->orWhere(
|
||||
$qbSelectClientId->expr()->iLike('redirect_uri', $qbDeleteAccessTokens->createNamedParameter('%*', IQueryBuilder::PARAM_STR))
|
||||
);
|
||||
}
|
||||
|
||||
$qbDeleteAccessTokens->delete('oauth2_access_tokens')
|
||||
->where(
|
||||
|
@ -194,10 +203,12 @@ class MigrateOauthTables implements IRepairStep {
|
|||
$qbDeleteClients->delete('oauth2_clients')
|
||||
->where(
|
||||
$qbDeleteClients->expr()->iLike('redirect_uri', $qbDeleteClients->createNamedParameter('oc://%', IQueryBuilder::PARAM_STR))
|
||||
)
|
||||
->orWhere(
|
||||
);
|
||||
if (!$enableOcClients) {
|
||||
$qbDeleteClients->orWhere(
|
||||
$qbDeleteClients->expr()->iLike('redirect_uri', $qbDeleteClients->createNamedParameter('%*', IQueryBuilder::PARAM_STR))
|
||||
);
|
||||
}
|
||||
$qbDeleteClients->executeStatement();
|
||||
|
||||
// Migrate legacy refresh tokens from oc
|
||||
|
|
|
@ -22147,6 +22147,15 @@
|
|||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "redirect_uri",
|
||||
"in": "query",
|
||||
"description": "URI to redirect to after the flow (is only used for legacy ownCloud clients)",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
|
|
@ -22,6 +22,7 @@ use OCP\AppFramework\Http\StandaloneTemplateResponse;
|
|||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\Defaults;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
use OCP\ISession;
|
||||
|
@ -48,6 +49,7 @@ class ClientFlowLoginControllerTest extends TestCase {
|
|||
private ICrypto&MockObject $crypto;
|
||||
private IEventDispatcher&MockObject $eventDispatcher;
|
||||
private ITimeFactory&MockObject $timeFactory;
|
||||
private IConfig&MockObject $config;
|
||||
|
||||
private ClientFlowLoginController $clientFlowLoginController;
|
||||
|
||||
|
@ -73,6 +75,7 @@ class ClientFlowLoginControllerTest extends TestCase {
|
|||
$this->crypto = $this->createMock(ICrypto::class);
|
||||
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
|
||||
$this->timeFactory = $this->createMock(ITimeFactory::class);
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
|
||||
$this->clientFlowLoginController = new ClientFlowLoginController(
|
||||
'core',
|
||||
|
@ -89,6 +92,7 @@ class ClientFlowLoginControllerTest extends TestCase {
|
|||
$this->crypto,
|
||||
$this->eventDispatcher,
|
||||
$this->timeFactory,
|
||||
$this->config,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -163,7 +167,8 @@ class ClientFlowLoginControllerTest extends TestCase {
|
|||
'serverHost' => 'https://example.com',
|
||||
'oauthState' => 'OauthStateToken',
|
||||
'user' => '',
|
||||
'direct' => 0
|
||||
'direct' => 0,
|
||||
'providedRedirectUri' => '',
|
||||
],
|
||||
'guest'
|
||||
);
|
||||
|
@ -233,7 +238,8 @@ class ClientFlowLoginControllerTest extends TestCase {
|
|||
'serverHost' => 'https://example.com',
|
||||
'oauthState' => 'OauthStateToken',
|
||||
'user' => '',
|
||||
'direct' => 0
|
||||
'direct' => 0,
|
||||
'providedRedirectUri' => '',
|
||||
],
|
||||
'guest'
|
||||
);
|
||||
|
|
Loading…
Add table
Reference in a new issue