Skip to content

Commit 049e0d9

Browse files
committed
fix(federation): check if app is enabled
before attempting any queries or other code paths Signed-off-by: Anna Larch <anna@nextcloud.com>
1 parent ef6d68f commit 049e0d9

2 files changed

Lines changed: 794 additions & 23 deletions

File tree

lib/Controller/RemoteActivityController.php

Lines changed: 71 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,22 @@
77

88
namespace OCA\Activity\Controller;
99

10+
use DateTimeInterface;
11+
use OC\Files\Storage\Wrapper\Wrapper;
1012
use OCA\Activity\Extension\Files;
13+
use OCA\Files_Sharing\External\Storage as ExternalStorage;
1114
use OCP\Activity\IManager as IActivityManager;
1215
use OCP\App\IAppManager;
1316
use OCP\AppFramework\Http;
17+
use OCP\AppFramework\Http\Attribute\BruteForceProtection;
1418
use OCP\AppFramework\Http\DataResponse;
1519
use OCP\AppFramework\OCSController;
20+
use OCP\AppFramework\Utility\ITimeFactory;
21+
use OCP\Federation\ICloudIdManager;
22+
use OCP\Files\IFilenameValidator;
1623
use OCP\Files\InvalidPathException;
1724
use OCP\Files\IRootFolder;
25+
use OCP\Files\ISetupManager;
1826
use OCP\Files\NotFoundException;
1927
use OCP\IDBConnection;
2028
use OCP\IRequest;
@@ -30,6 +38,10 @@ public function __construct(
3038
protected IAppManager $appManager,
3139
protected IRootFolder $rootFolder,
3240
protected IActivityManager $activityManager,
41+
protected ICloudIdManager $cloudIdManager,
42+
protected IFilenameValidator $filenameValidator,
43+
protected ISetupManager $setupManager,
44+
protected ITimeFactory $timeFactory,
3345
) {
3446
parent::__construct($appName, $request);
3547
}
@@ -48,20 +60,26 @@ public function __construct(
4860
* @param string[] $origin
4961
* @return DataResponse
5062
*/
51-
public function receiveActivity($token, array $to, array $actor, $type, $updated, array $object = [], array $target = [], array $origin = []) {
52-
$date = \DateTime::createFromFormat(\DateTime::W3C, $updated);
53-
if ($date === false) {
63+
#[BruteForceProtection(action: 'receiveActivity')]
64+
public function receiveActivity($token, array $to, array $actor, $type, $updated, array $object = [], array $target = [], array $origin = []): DataResponse {
65+
if (!$this->appManager->isEnabledForAnyone('federatedfilesharing')) {
66+
return new DataResponse([], Http::STATUS_NOT_FOUND);
67+
}
68+
69+
$date = \DateTime::createFromFormat(DateTimeInterface::W3C, $updated);
70+
if ($date === false || abs($date->getTimestamp() - $this->timeFactory->getTime()) > 600) {
5471
return new DataResponse([], Http::STATUS_BAD_REQUEST);
5572
}
56-
$time = $date->getTimestamp();
5773

5874
if (!isset($to['type'], $to['name']) || $to['type'] !== 'Person') {
5975
return new DataResponse([], Http::STATUS_BAD_REQUEST);
6076
}
6177

6278
$user = $this->userManager->get($to['name']);
6379
if (!$user instanceof IUser) {
64-
return new DataResponse([], Http::STATUS_NOT_FOUND);
80+
$response = new DataResponse([], Http::STATUS_NOT_FOUND);
81+
$response->throttle();
82+
return $response;
6583
}
6684

6785
if (!isset($actor['type'], $actor['name']) || $actor['type'] !== 'Person') {
@@ -72,27 +90,44 @@ public function receiveActivity($token, array $to, array $actor, $type, $updated
7290
return new DataResponse([], Http::STATUS_BAD_REQUEST);
7391
}
7492

75-
if (!$this->appManager->isInstalled('federatedfilesharing')) {
76-
return new DataResponse([], Http::STATUS_NOT_FOUND);
93+
if (isset($object['name']) && preg_match('/(^|\/)\.\.(\/|$)/', $object['name'])) {
94+
return new DataResponse([], Http::STATUS_BAD_REQUEST);
95+
}
96+
97+
try {
98+
$resolved = $this->cloudIdManager->resolveCloudId($actor['name']);
99+
$actorServer = $resolved->getRemote();
100+
$actorUser = $resolved->getUser();
101+
} catch (\InvalidArgumentException) {
102+
return new DataResponse([], Http::STATUS_BAD_REQUEST);
103+
}
104+
105+
$internalType = $this->translateType($type);
106+
if ($internalType === '') {
107+
return new DataResponse([], Http::STATUS_BAD_REQUEST);
77108
}
78109

79110
$query = $this->db->getQueryBuilder();
80111
$query->select('*')
81112
->from('share_external')
82113
->where($query->expr()->eq('share_token', $query->createNamedParameter($token)))
83-
->andWhere($query->expr()->eq('user', $query->createNamedParameter($user->getUID())));
114+
->andWhere($query->expr()->eq('user', $query->createNamedParameter($user->getUID())))
115+
->andWhere($query->expr()->eq('owner', $query->createNamedParameter($actorUser)));
84116

85117
$result = $query->executeQuery();
86118
$share = $result->fetch();
87119
$result->closeCursor();
88120

89-
if (!is_array($share) || strpos($share['mountpoint'], '{{TemporaryMountPointName#') === 0) {
90-
return new DataResponse([], Http::STATUS_NOT_FOUND);
121+
if (!is_array($share) || str_starts_with($share['mountpoint'], '{{TemporaryMountPointName#')) {
122+
$response = new DataResponse([], Http::STATUS_NOT_FOUND);
123+
$response->throttle();
124+
return $response;
91125
}
92126

93-
$internalType = $this->translateType($type);
94-
if ($internalType === '') {
95-
return new DataResponse([], Http::STATUS_BAD_REQUEST);
127+
$normalizedActorServer = rtrim(strtolower(preg_replace('/^https?:\/\//', '', $actorServer)), '/');
128+
$normalizedShareRemote = rtrim(strtolower(preg_replace('/^https?:\/\//', '', $share['remote'])), '/');
129+
if ($normalizedActorServer !== $normalizedShareRemote) {
130+
return new DataResponse([], Http::STATUS_FORBIDDEN);
96131
}
97132

98133
$path2 = null;
@@ -111,7 +146,6 @@ public function receiveActivity($token, array $to, array $actor, $type, $updated
111146
if (!isset($object['type'], $object['name']) || $object['type'] !== 'Document') {
112147
return new DataResponse([], Http::STATUS_BAD_REQUEST);
113148
}
114-
115149
$path = $share['mountpoint'] . $object['name'];
116150
}
117151

@@ -124,10 +158,25 @@ public function receiveActivity($token, array $to, array $actor, $type, $updated
124158
try {
125159
$node = $userFolder->get($path);
126160
$fileId = $node->getId();
127-
} catch (NotFoundException $e) {
128-
return new DataResponse([], Http::STATUS_NOT_FOUND);
129-
} catch (InvalidPathException $e) {
130-
return new DataResponse([], Http::STATUS_NOT_FOUND);
161+
162+
$storage = $node->getStorage();
163+
if (!$storage->instanceOfStorage(ExternalStorage::class)) {
164+
$response = new DataResponse([], Http::STATUS_FORBIDDEN);
165+
$response->throttle();
166+
return $response;
167+
}
168+
while ($storage instanceof Wrapper) {
169+
$storage = $storage->getWrapperStorage();
170+
}
171+
if (!($storage instanceof ExternalStorage) || $storage->getToken() !== $token) {
172+
$response = new DataResponse([], Http::STATUS_FORBIDDEN);
173+
$response->throttle();
174+
return $response;
175+
}
176+
} catch (NotFoundException|InvalidPathException) {
177+
$response = new DataResponse([], Http::STATUS_NOT_FOUND);
178+
$response->throttle();
179+
return $response;
131180
}
132181

133182
if ($path2 !== null) {
@@ -136,8 +185,7 @@ public function receiveActivity($token, array $to, array $actor, $type, $updated
136185
try {
137186
$parent = $node->getParent();
138187
$secondPath = [$parent->getId() => dirname($path2)];
139-
} catch (NotFoundException $e) {
140-
} catch (InvalidPathException $e) {
188+
} catch (NotFoundException|InvalidPathException) {
141189
}
142190
}
143191
$subjectParams = [$secondPath, $actor['name'], [$fileId => $path]];
@@ -153,11 +201,11 @@ public function receiveActivity($token, array $to, array $actor, $type, $updated
153201
->setAuthor($actor['name'])
154202
->setObject('files', $fileId, $path)
155203
->setSubject($subject, $subjectParams)
156-
->setTimestamp($time);
204+
->setTimestamp($date->getTimestamp());
157205
$this->activityManager->publish($event);
158-
} catch (\InvalidArgumentException $e) {
206+
} catch (\InvalidArgumentException) {
159207
return new DataResponse(['activity'], Http::STATUS_BAD_REQUEST);
160-
} catch (\BadMethodCallException $e) {
208+
} catch (\BadMethodCallException) {
161209
return new DataResponse(['sending'], Http::STATUS_BAD_REQUEST);
162210
}
163211

0 commit comments

Comments
 (0)