Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/dav/appinfo/v1/webdav.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@
$bearerAuthPlugin = new \OCA\DAV\Connector\Sabre\BearerAuth(
\OC::$server->getUserSession(),
\OC::$server->getSession(),
\OC::$server->getRequest()
\OC::$server->getRequest(),
\OC::$server->getConfig(),
);
$authPlugin->addBackend($bearerAuthPlugin);

Expand Down
12 changes: 12 additions & 0 deletions apps/dav/lib/Connector/Sabre/BearerAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/
namespace OCA\DAV\Connector\Sabre;

use OCP\IConfig;
use OCP\IRequest;
use OCP\ISession;
use OCP\IUserSession;
Expand All @@ -34,15 +35,18 @@ class BearerAuth extends AbstractBearer {
private IUserSession $userSession;
private ISession $session;
private IRequest $request;
private IConfig $config;
private string $principalPrefix;

public function __construct(IUserSession $userSession,
ISession $session,
IRequest $request,
IConfig $config,
$principalPrefix = 'principals/users/') {
$this->userSession = $userSession;
$this->session = $session;
$this->request = $request;
$this->config = $config;
$this->principalPrefix = $principalPrefix;

// setup realm
Expand Down Expand Up @@ -81,6 +85,14 @@ public function validateBearerToken($bearerToken) {
* @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(401);
}
}
3 changes: 2 additions & 1 deletion apps/dav/lib/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@
$bearerAuthBackend = new BearerAuth(
\OC::$server->getUserSession(),
\OC::$server->getSession(),
\OC::$server->getRequest()
\OC::$server->getRequest(),
\OC::$server->getConfig(),
);
$authPlugin->addBackend($bearerAuthBackend);
// because we are throwing exceptions this plugin has to be the last one
Expand Down
7 changes: 6 additions & 1 deletion apps/dav/tests/unit/Connector/Sabre/BearerAuthTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
namespace OCA\DAV\Tests\unit\Connector\Sabre;

use OCA\DAV\Connector\Sabre\BearerAuth;
use OCP\IConfig;
use OCP\IRequest;
use OCP\ISession;
use OCP\IUser;
Expand All @@ -45,18 +46,22 @@ class BearerAuthTest extends TestCase {
private $request;
/** @var BearerAuth */
private $bearerAuth;
/** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
private $config;

protected function setUp(): void {
parent::setUp();

$this->userSession = $this->createMock(\OC\User\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,
);
}

Expand Down
4 changes: 4 additions & 0 deletions apps/oauth2/appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
</post-migration>
</repair-steps>

<commands>
<command>OCA\OAuth2\Command\ImportLegacyOcClient</command>
</commands>

<settings>
<admin>OCA\OAuth2\Settings\Admin</admin>
</settings>
Expand Down
1 change: 1 addition & 0 deletions apps/oauth2/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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',
Expand Down
1 change: 1 addition & 0 deletions apps/oauth2/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
76 changes: 76 additions & 0 deletions apps/oauth2/lib/Command/ImportLegacyOcClient.php
Original file line number Diff line number Diff line change
@@ -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 IConfig $config,
private ICrypto $crypto,
private 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;
}
}
19 changes: 17 additions & 2 deletions apps/oauth2/lib/Controller/LoginRedirectorController.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IRequest;
use OCP\ISession;
Expand All @@ -48,6 +49,7 @@ class LoginRedirectorController extends Controller {
private $session;
/** @var IL10N */
private $l;
private IConfig $config;

/**
* @param string $appName
Expand All @@ -56,18 +58,21 @@ class LoginRedirectorController extends Controller {
* @param ClientMapper $clientMapper
* @param ISession $session
* @param IL10N $l
* @param IConfig $config
*/
public function __construct(string $appName,
IRequest $request,
IURLGenerator $urlGenerator,
ClientMapper $clientMapper,
ISession $session,
IL10N $l) {
IL10N $l,
IConfig $config) {
parent::__construct($appName, $request);
$this->urlGenerator = $urlGenerator;
$this->clientMapper = $clientMapper;
$this->session = $session;
$this->l = $l;
$this->config = $config;
}

/**
Expand All @@ -80,14 +85,16 @@ public function __construct(string $appName,
* @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
* 303: Redirect to login URL
*/
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) {
Expand All @@ -103,12 +110,20 @@ public function authorize($client_id,
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(),
'providedRedirectUri' => $providedRedirectUri,
]
);
return new RedirectResponse($targetUrl);
Expand Down
9 changes: 9 additions & 0 deletions apps/oauth2/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Loading
Loading