Skip to content

Commit caa1855

Browse files
authored
Merge pull request #65340 from nextcloud/feat/noid/app-password-route-gate
feat(auth): limit scoped app passwords to the routes they open
2 parents f66f729 + d7c0abc commit caa1855

40 files changed

Lines changed: 614 additions & 6 deletions

‎apps/dav/composer/composer/autoload_classmap.php‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@
461461
'OCA\\DAV\\SystemTag\\SystemTagsObjectMappingCollection' => $baseDir . '/../lib/SystemTag/SystemTagsObjectMappingCollection.php',
462462
'OCA\\DAV\\SystemTag\\SystemTagsObjectTypeCollection' => $baseDir . '/../lib/SystemTag/SystemTagsObjectTypeCollection.php',
463463
'OCA\\DAV\\SystemTag\\SystemTagsRelationsCollection' => $baseDir . '/../lib/SystemTag/SystemTagsRelationsCollection.php',
464+
'OCA\\DAV\\TokenScopes' => $baseDir . '/../lib/TokenScopes.php',
464465
'OCA\\DAV\\Traits\\PrincipalProxyTrait' => $baseDir . '/../lib/Traits/PrincipalProxyTrait.php',
465466
'OCA\\DAV\\Upload\\AssemblyStream' => $baseDir . '/../lib/Upload/AssemblyStream.php',
466467
'OCA\\DAV\\Upload\\ChunkingPlugin' => $baseDir . '/../lib/Upload/ChunkingPlugin.php',

‎apps/dav/composer/composer/autoload_static.php‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,7 @@ class ComposerStaticInitDAV
476476
'OCA\\DAV\\SystemTag\\SystemTagsObjectMappingCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsObjectMappingCollection.php',
477477
'OCA\\DAV\\SystemTag\\SystemTagsObjectTypeCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsObjectTypeCollection.php',
478478
'OCA\\DAV\\SystemTag\\SystemTagsRelationsCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsRelationsCollection.php',
479+
'OCA\\DAV\\TokenScopes' => __DIR__ . '/..' . '/../lib/TokenScopes.php',
479480
'OCA\\DAV\\Traits\\PrincipalProxyTrait' => __DIR__ . '/..' . '/../lib/Traits/PrincipalProxyTrait.php',
480481
'OCA\\DAV\\Upload\\AssemblyStream' => __DIR__ . '/..' . '/../lib/Upload/AssemblyStream.php',
481482
'OCA\\DAV\\Upload\\ChunkingPlugin' => __DIR__ . '/..' . '/../lib/Upload/ChunkingPlugin.php',

‎apps/dav/lib/Controller/CalendarExportController.php‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88

99
namespace OCA\DAV\Controller;
1010

11+
use NCU\AppFramework\Http\Attribute\TokenScope;
1112
use OCA\DAV\AppInfo\Application;
1213
use OCA\DAV\CalDAV\Export\ExportService;
14+
use OCA\DAV\TokenScopes;
1315
use OCP\AppFramework\Http;
1416
use OCP\AppFramework\Http\Attribute\ApiRoute;
1517
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
@@ -55,6 +57,7 @@ public function __construct(
5557
#[ApiRoute(verb: 'POST', url: '/export', root: '/calendar')]
5658
#[UserRateLimit(limit: 1, period: 60)]
5759
#[NoAdminRequired]
60+
#[TokenScope(scope: TokenScopes::CALENDAR_READ)]
5861
public function export(string $target, ?string $type = null, ?array $options = null, ?string $user = null) {
5962
$calendarId = $target;
6063
$format = $type ?? 'ical';

‎apps/dav/lib/Controller/CalendarImportController.php‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99
namespace OCA\DAV\Controller;
1010

1111
use InvalidArgumentException;
12+
use NCU\AppFramework\Http\Attribute\TokenScope;
1213
/**
1314
* @psalm-type CalendarImportResult = array{items: list<string>, total: non-negative-int}
1415
*/
1516
use OCA\DAV\AppInfo\Application;
1617
use OCA\DAV\CalDAV\CalendarImpl;
1718
use OCA\DAV\CalDAV\Import\ImportService;
19+
use OCA\DAV\TokenScopes;
1820
use OCP\AppFramework\Http;
1921
use OCP\AppFramework\Http\Attribute\ApiRoute;
2022
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
@@ -62,6 +64,7 @@ public function __construct(
6264
#[ApiRoute(verb: 'POST', url: '/import', root: '/calendar')]
6365
#[UserRateLimit(limit: 10, period: 3600)]
6466
#[NoAdminRequired]
67+
#[TokenScope(scope: TokenScopes::CALENDAR_WRITE)]
6568
public function import(string $transaction, string $target, array $options, string $data, ?string $user = null): DataResponse|StreamTraversableResponse {
6669
$calendarId = $target;
6770
$format = isset($options['format']) ? $options['format'] : null;

‎apps/dav/lib/Controller/ContactsImportController.php‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
namespace OCA\DAV\Controller;
1010

1111
use InvalidArgumentException;
12+
use NCU\AppFramework\Http\Attribute\TokenScope;
1213
/**
1314
* @psalm-type ContactsImportResult = array{items: list<string>, total: non-negative-int}
1415
*/
1516
use OCA\DAV\AppInfo\Application;
1617
use OCA\DAV\CardDAV\Import\ImportService;
18+
use OCA\DAV\TokenScopes;
1719
use OCP\AppFramework\Http;
1820
use OCP\AppFramework\Http\Attribute\ApiRoute;
1921
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
@@ -62,6 +64,7 @@ public function __construct(
6264
#[ApiRoute(verb: 'POST', url: '/import', root: '/contacts')]
6365
#[UserRateLimit(limit: 10, period: 3600)]
6466
#[NoAdminRequired]
67+
#[TokenScope(scope: TokenScopes::CONTACTS_WRITE)]
6568
public function import(string $transaction, string $target, array $options, string $data, ?string $user = null): DataResponse|StreamTraversableResponse {
6669
$addressBookId = $target;
6770
$format = isset($options['format']) ? $options['format'] : null;

‎apps/dav/lib/Controller/DirectController.php‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
namespace OCA\DAV\Controller;
1111

12+
use NCU\AppFramework\Http\Attribute\TokenScope;
1213
use OCA\DAV\Db\Direct;
1314
use OCA\DAV\Db\DirectMapper;
1415
use OCP\AppFramework\Http;
@@ -58,6 +59,8 @@ public function __construct(
5859
* 200: Direct link returned
5960
*/
6061
#[NoAdminRequired]
62+
// Not OCA\Files_Sharing\TokenScopes::SHARES_WRITE: files_sharing can be disabled, and its classes don't load then
63+
#[TokenScope(scope: 'files_sharing:shares:write')]
6164
public function getUrl(int $fileId, int $expirationTime = 60 * 60 * 8): DataResponse {
6265
if (!$this->shareManager->shareApiAllowLinks()) {
6366
throw new OCSForbiddenException('Creating direct links is disabled');

‎apps/dav/lib/Controller/FederatedCalendarController.php‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99

1010
namespace OCA\DAV\Controller;
1111

12+
use NCU\AppFramework\Http\Attribute\TokenScope;
1213
use OCA\DAV\AppInfo\Application;
1314
use OCA\DAV\CalDAV\Federation\FederatedCalendarEntity;
1415
use OCA\DAV\CalDAV\Federation\FederatedCalendarInvitationService;
1516
use OCA\DAV\CalDAV\Federation\FederatedCalendarMapper;
1617
use OCA\DAV\ResponseDefinitions;
18+
use OCA\DAV\TokenScopes;
1719
use OCP\AppFramework\Db\DoesNotExistException;
1820
use OCP\AppFramework\Http;
1921
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
@@ -44,6 +46,7 @@ public function __construct(
4446
* 401: When the user is not logged in
4547
*/
4648
#[NoAdminRequired]
49+
#[TokenScope(scope: TokenScopes::CALENDAR_READ)]
4750
public function getPending(): DataResponse {
4851
if ($this->userId === null) {
4952
return new DataResponse(null, Http::STATUS_UNAUTHORIZED);
@@ -76,6 +79,7 @@ public function getPending(): DataResponse {
7679
* 200: Invitation accepted successfully
7780
*/
7881
#[NoAdminRequired]
82+
#[TokenScope(scope: TokenScopes::CALENDAR_WRITE)]
7983
public function accept(int $id): DataResponse {
8084
$this->invitationService->accept($this->getPendingCalendar($id));
8185
return new DataResponse(null);
@@ -91,6 +95,7 @@ public function accept(int $id): DataResponse {
9195
* 200: Invitation declined successfully
9296
*/
9397
#[NoAdminRequired]
98+
#[TokenScope(scope: TokenScopes::CALENDAR_WRITE)]
9499
public function decline(int $id): DataResponse {
95100
$this->invitationService->decline($this->getPendingCalendar($id));
96101
return new DataResponse(null);

‎apps/dav/lib/Controller/OutOfOfficeController.php‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
namespace OCA\DAV\Controller;
1111

1212
use DateTimeImmutable;
13+
use NCU\AppFramework\Http\Attribute\TokenScope;
1314
use OCA\DAV\ResponseDefinitions;
1415
use OCA\DAV\Service\AbsenceService;
16+
use OCA\DAV\TokenScopes;
1517
use OCP\AppFramework\Db\DoesNotExistException;
1618
use OCP\AppFramework\Http;
1719
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
@@ -50,6 +52,7 @@ public function __construct(
5052
* 404: No out-of-office data was found
5153
*/
5254
#[NoAdminRequired]
55+
#[TokenScope(scope: TokenScopes::CALENDAR_READ)]
5356
public function getCurrentOutOfOfficeData(string $userId): DataResponse {
5457
$user = $this->userManager->get($userId);
5558
if ($user === null) {
@@ -77,6 +80,7 @@ public function getCurrentOutOfOfficeData(string $userId): DataResponse {
7780
* 404: No out-of-office data was found
7881
*/
7982
#[NoAdminRequired]
83+
#[TokenScope(scope: TokenScopes::CALENDAR_READ)]
8084
public function getOutOfOffice(string $userId): DataResponse {
8185
try {
8286
$data = $this->absenceService->getAbsence($userId);
@@ -115,6 +119,7 @@ public function getOutOfOffice(string $userId): DataResponse {
115119
* 404: When the replacementUserId was provided but replacement user was not found
116120
*/
117121
#[NoAdminRequired]
122+
#[TokenScope(scope: TokenScopes::CALENDAR_WRITE)]
118123
public function setOutOfOffice(
119124
string $firstDay,
120125
string $lastDay,
@@ -176,6 +181,7 @@ public function setOutOfOffice(
176181
* 401: When the user is not logged in
177182
*/
178183
#[NoAdminRequired]
184+
#[TokenScope(scope: TokenScopes::CALENDAR_WRITE)]
179185
public function clearOutOfOffice(): DataResponse {
180186
$user = $this->userSession?->getUser();
181187
if ($user === null) {

‎apps/dav/lib/Controller/UpcomingEventsController.php‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99

1010
namespace OCA\DAV\Controller;
1111

12+
use NCU\AppFramework\Http\Attribute\TokenScope;
1213
use OCA\DAV\AppInfo\Application;
1314
use OCA\DAV\CalDAV\UpcomingEvent;
1415
use OCA\DAV\CalDAV\UpcomingEventsService;
1516
use OCA\DAV\ResponseDefinitions;
17+
use OCA\DAV\TokenScopes;
1618
use OCP\AppFramework\Http;
1719
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
1820
use OCP\AppFramework\Http\DataResponse;
@@ -41,6 +43,7 @@ public function __construct(
4143
* 401: When not authenticated
4244
*/
4345
#[NoAdminRequired]
46+
#[TokenScope(scope: TokenScopes::CALENDAR_READ)]
4447
public function getEvents(?string $location = null): DataResponse {
4548
if ($this->userId === null) {
4649
return new DataResponse(null, Http::STATUS_UNAUTHORIZED);

‎apps/dav/lib/TokenScopes.php‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\DAV;
11+
12+
/**
13+
* App password scopes for calendars and address books
14+
*/
15+
class TokenScopes {
16+
public const CALENDAR_READ = 'dav:calendar:read';
17+
public const CALENDAR_WRITE = 'dav:calendar:write';
18+
public const CONTACTS_READ = 'dav:contacts:read';
19+
public const CONTACTS_WRITE = 'dav:contacts:write';
20+
}

0 commit comments

Comments
 (0)