Skip to content

Commit 2a17919

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 26f61f8 commit 2a17919

File tree

3 files changed

+894
-24
lines changed

3 files changed

+894
-24
lines changed

lib/Controller/RemoteActivityController.php

Lines changed: 67 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,18 @@
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;
1622
use OCP\Files\InvalidPathException;
1723
use OCP\Files\IRootFolder;
1824
use OCP\Files\NotFoundException;
@@ -30,6 +36,8 @@ public function __construct(
3036
protected IAppManager $appManager,
3137
protected IRootFolder $rootFolder,
3238
protected IActivityManager $activityManager,
39+
protected ICloudIdManager $cloudIdManager,
40+
protected ITimeFactory $timeFactory,
3341
) {
3442
parent::__construct($appName, $request);
3543
}
@@ -48,20 +56,26 @@ public function __construct(
4856
* @param string[] $origin
4957
* @return DataResponse
5058
*/
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) {
59+
#[BruteForceProtection(action: 'receiveActivity')]
60+
public function receiveActivity($token, array $to, array $actor, $type, $updated, array $object = [], array $target = [], array $origin = []): DataResponse {
61+
if (!$this->appManager->isEnabledForAnyone('federatedfilesharing')) {
62+
return new DataResponse([], Http::STATUS_NOT_FOUND);
63+
}
64+
65+
$date = \DateTime::createFromFormat(DateTimeInterface::W3C, $updated);
66+
if ($date === false || abs($date->getTimestamp() - $this->timeFactory->getTime()) > 600) {
5467
return new DataResponse([], Http::STATUS_BAD_REQUEST);
5568
}
56-
$time = $date->getTimestamp();
5769

5870
if (!isset($to['type'], $to['name']) || $to['type'] !== 'Person') {
5971
return new DataResponse([], Http::STATUS_BAD_REQUEST);
6072
}
6173

6274
$user = $this->userManager->get($to['name']);
6375
if (!$user instanceof IUser) {
64-
return new DataResponse([], Http::STATUS_NOT_FOUND);
76+
$response = new DataResponse([], Http::STATUS_NOT_FOUND);
77+
$response->throttle();
78+
return $response;
6579
}
6680

6781
if (!isset($actor['type'], $actor['name']) || $actor['type'] !== 'Person') {
@@ -72,27 +86,44 @@ public function receiveActivity($token, array $to, array $actor, $type, $updated
7286
return new DataResponse([], Http::STATUS_BAD_REQUEST);
7387
}
7488

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

79106
$query = $this->db->getQueryBuilder();
80107
$query->select('*')
81108
->from('share_external')
82109
->where($query->expr()->eq('share_token', $query->createNamedParameter($token)))
83-
->andWhere($query->expr()->eq('user', $query->createNamedParameter($user->getUID())));
110+
->andWhere($query->expr()->eq('user', $query->createNamedParameter($user->getUID())))
111+
->andWhere($query->expr()->eq('owner', $query->createNamedParameter($actorUser)));
84112

85113
$result = $query->executeQuery();
86114
$share = $result->fetch();
87115
$result->closeCursor();
88116

89-
if (!is_array($share) || strpos($share['mountpoint'], '{{TemporaryMountPointName#') === 0) {
90-
return new DataResponse([], Http::STATUS_NOT_FOUND);
117+
if (!is_array($share) || str_starts_with($share['mountpoint'], '{{TemporaryMountPointName#')) {
118+
$response = new DataResponse([], Http::STATUS_NOT_FOUND);
119+
$response->throttle();
120+
return $response;
91121
}
92122

93-
$internalType = $this->translateType($type);
94-
if ($internalType === '') {
95-
return new DataResponse([], Http::STATUS_BAD_REQUEST);
123+
$normalizedActorServer = rtrim(strtolower(preg_replace('/^https?:\/\//', '', $actorServer)), '/');
124+
$normalizedShareRemote = rtrim(strtolower(preg_replace('/^https?:\/\//', '', $share['remote'])), '/');
125+
if ($normalizedActorServer !== $normalizedShareRemote) {
126+
return new DataResponse([], Http::STATUS_FORBIDDEN);
96127
}
97128

98129
$path2 = null;
@@ -111,7 +142,6 @@ public function receiveActivity($token, array $to, array $actor, $type, $updated
111142
if (!isset($object['type'], $object['name']) || $object['type'] !== 'Document') {
112143
return new DataResponse([], Http::STATUS_BAD_REQUEST);
113144
}
114-
115145
$path = $share['mountpoint'] . $object['name'];
116146
}
117147

@@ -124,10 +154,25 @@ public function receiveActivity($token, array $to, array $actor, $type, $updated
124154
try {
125155
$node = $userFolder->get($path);
126156
$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);
157+
158+
$storage = $node->getStorage();
159+
if (!$storage->instanceOfStorage(ExternalStorage::class)) {
160+
$response = new DataResponse([], Http::STATUS_FORBIDDEN);
161+
$response->throttle();
162+
return $response;
163+
}
164+
while ($storage instanceof Wrapper) {
165+
$storage = $storage->getWrapperStorage();
166+
}
167+
if (!($storage instanceof ExternalStorage) || $storage->getToken() !== $token) {
168+
$response = new DataResponse([], Http::STATUS_FORBIDDEN);
169+
$response->throttle();
170+
return $response;
171+
}
172+
} catch (NotFoundException|InvalidPathException) {
173+
$response = new DataResponse([], Http::STATUS_NOT_FOUND);
174+
$response->throttle();
175+
return $response;
131176
}
132177

133178
if ($path2 !== null) {
@@ -136,8 +181,7 @@ public function receiveActivity($token, array $to, array $actor, $type, $updated
136181
try {
137182
$parent = $node->getParent();
138183
$secondPath = [$parent->getId() => dirname($path2)];
139-
} catch (NotFoundException $e) {
140-
} catch (InvalidPathException $e) {
184+
} catch (NotFoundException|InvalidPathException) {
141185
}
142186
}
143187
$subjectParams = [$secondPath, $actor['name'], [$fileId => $path]];
@@ -153,11 +197,11 @@ public function receiveActivity($token, array $to, array $actor, $type, $updated
153197
->setAuthor($actor['name'])
154198
->setObject('files', $fileId, $path)
155199
->setSubject($subject, $subjectParams)
156-
->setTimestamp($time);
200+
->setTimestamp($date->getTimestamp());
157201
$this->activityManager->publish($event);
158-
} catch (\InvalidArgumentException $e) {
202+
} catch (\InvalidArgumentException) {
159203
return new DataResponse(['activity'], Http::STATUS_BAD_REQUEST);
160-
} catch (\BadMethodCallException $e) {
204+
} catch (\BadMethodCallException) {
161205
return new DataResponse(['sending'], Http::STATUS_BAD_REQUEST);
162206
}
163207

0 commit comments

Comments
 (0)