Skip to content

Commit 9ab7716

Browse files
authored
Merge pull request #1031 from nextcloud/backport/992/992-stable23
[stable23] groupfolders+acl
2 parents 14f175f + cb8e4a9 commit 9ab7716

File tree

1 file changed

+161
-13
lines changed

1 file changed

+161
-13
lines changed

lib/FilesHooks.php

Lines changed: 161 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@
3131
use OCA\Activity\Extension\Files;
3232
use OCA\Activity\Extension\Files_Sharing;
3333
use OCP\Activity\IManager;
34-
use OCP\Files\Config\ICachedMountFileInfo;
35-
use OCP\Files\Config\ICachedMountInfo;
34+
use OCP\Constants;
3635
use OCP\Files\Config\IUserMountCache;
3736
use OCP\Files\IRootFolder;
3837
use OCP\Files\Mount\IMountPoint;
@@ -233,20 +232,15 @@ protected function addNotificationsForFileAction($filePath, $activityType, $subj
233232
$this->generateRemoteActivity($accessList['remotes'], $activityType, time(), $this->currentUser->getCloudId(), $accessList['ownerPath']);
234233
}
235234

235+
$affectedUsers = $accessList['users'];
236+
237+
// file can be shared using GroupFolders, including ACL check
236238
if ($this->config->getSystemValueBool('activity_use_cached_mountpoints', false)) {
237-
$mountsForFile = $this->userMountCache->getMountsForFileId($fileId);
238-
$affectedUserIds = array_map(function (ICachedMountInfo $mount) {
239-
return $mount->getUser()->getUID();
240-
}, $mountsForFile);
241-
$affectedPaths = array_map(function (ICachedMountFileInfo $mount) {
242-
return $this->getVisiblePath($mount->getPath());
243-
}, $mountsForFile);
244-
$affectedUsers = array_combine($affectedUserIds, $affectedPaths);
245-
} else {
246-
$affectedUsers = $accessList['users'];
239+
$affectedUsers = array_merge($affectedUsers, $this->getAffectedUsersFromCachedMounts($fileId));
247240
}
248241

249-
[$filteredEmailUsers, $filteredNotificationUsers] = $this->getFileChangeActivitySettings($fileId, array_keys($affectedUsers));
242+
[$filteredEmailUsers, $filteredNotificationUsers] =
243+
$this->getFileChangeActivitySettings($fileId, array_keys($affectedUsers));
250244

251245
foreach ($affectedUsers as $user => $path) {
252246
$user = (string)$user;
@@ -1219,4 +1213,158 @@ protected function commitActivityTransaction(bool $shouldFlush): void {
12191213
}
12201214
$this->connection->commit();
12211215
}
1216+
1217+
1218+
/**
1219+
* @param int $fileId
1220+
*
1221+
* @return array
1222+
*/
1223+
private function getAffectedUsersFromCachedMounts(int $fileId): array {
1224+
$affectedUsers = $cachedMounts = [];
1225+
$mountsForFile = $this->userMountCache->getMountsForFileId($fileId);
1226+
foreach ($mountsForFile as $mount) {
1227+
$affectedUsers[$mount->getUser()->getUID()] = $this->getVisiblePath($mount->getPath());
1228+
$cachedMounts[] = [
1229+
'userId' => $mount->getUser()->getUID(),
1230+
'path' => $mount->getPath(),
1231+
'visiblePath' => $this->getVisiblePath($mount->getPath()),
1232+
'storageId' => $mount->getStorageId()
1233+
];
1234+
}
1235+
1236+
$unrelatedUsers = $this->getUnrelatedUsers($fileId, $cachedMounts);
1237+
return array_filter($affectedUsers, function ($userId) use ($unrelatedUsers): bool {
1238+
return !in_array($userId, $unrelatedUsers);
1239+
}, ARRAY_FILTER_USE_KEY);
1240+
}
1241+
1242+
1243+
/**
1244+
* returns an array of users that have confirmed no access to fileId
1245+
*
1246+
* @param int $fileId
1247+
* @param array $cachedMounts
1248+
*
1249+
* @return string[] list of unrelated userIds
1250+
*/
1251+
private function getUnrelatedUsers(int $fileId, array $cachedMounts): array {
1252+
/** @var \OCA\GroupFolders\ACL\RuleManager $ruleManager */
1253+
try {
1254+
$ruleManager = \OC::$server->get(\OCA\GroupFolders\ACL\RuleManager::class);
1255+
} catch (\Exception $e) {
1256+
return []; // if we have no access to RuleManager, we cannot filter unrelated users
1257+
}
1258+
1259+
/** @var \OCA\GroupFolders\ACL\Rule[] $rules */
1260+
$rules = $knownRules = $knownGroupRules = $usersToCheck = $cachedPath = [];
1261+
foreach ($cachedMounts as $cachedMount) {
1262+
1263+
// caching rules based on storage id
1264+
$storageId = $cachedMount['storageId'];
1265+
if (!array_key_exists($storageId, $knownRules)) {
1266+
$knownRules[$storageId] = [];
1267+
}
1268+
1269+
$cachedPath[$cachedMount['userId']] = $fullPath = $cachedMount['path'];
1270+
1271+
// caching rules based on storage+path to file
1272+
if (!array_key_exists($cachedMount['visiblePath'], $knownRules[$storageId])) {
1273+
// we need mountPoint and folderId to generate the correct path
1274+
try {
1275+
$node = $this->rootFolder->get($fullPath);
1276+
$mountPoint = $node->getMountPoint();
1277+
1278+
if (!$mountPoint instanceof \OCA\GroupFolders\Mount\GroupMountPoint) {
1279+
continue;
1280+
}
1281+
1282+
$folderPath = trim($mountPoint->getSourcePath(), '/');
1283+
$path = substr($fullPath, strlen($mountPoint->getMountPoint()));
1284+
} catch (\Exception $e) {
1285+
1286+
// in case of issue during the process, we can imagine the user have no access to the file
1287+
$usersToCheck[] = $cachedMount['userId'];
1288+
continue; // we'll catch rules on next user with access to the file
1289+
}
1290+
1291+
// we generate a list of path from top level of group folder to the file itself to get all rules
1292+
$paths = [$folderPath];
1293+
while ($path !== '') {
1294+
$paths[] = $folderPath . '/' . $path;
1295+
$path = dirname($path);
1296+
if ($path === '.' || $path === '/') {
1297+
$path = '';
1298+
}
1299+
}
1300+
1301+
// we might already know the rules for some path of the list
1302+
$paths = array_filter($paths, function (string $path) use ($knownRules, $storageId): bool {
1303+
if (array_key_exists($path, $knownRules[$storageId])) {
1304+
return false;
1305+
}
1306+
1307+
return true;
1308+
});
1309+
1310+
// we get and store the rules for each path from the list
1311+
$rulesPerPath = $ruleManager->getAllRulesForPaths($storageId, $paths);
1312+
foreach (array_keys($rulesPerPath) as $path) {
1313+
$rules = array_merge($rules, $rulesPerPath[$path]);
1314+
}
1315+
1316+
$knownRules[$storageId][$cachedMount['visiblePath']] = true;
1317+
}
1318+
}
1319+
1320+
// using each rules that disable read permission to generate a list of users
1321+
// that might not have access to fileId
1322+
foreach ($rules as $rule) {
1323+
if (($rule->getMask() & Constants::PERMISSION_READ) === 0
1324+
|| ($rule->getPermissions() & Constants::PERMISSION_READ) !== 0) {
1325+
continue; // not interested of rules with 'mask' not including read capability (1), or if 'permission' does
1326+
}
1327+
1328+
$mapping = $rule->getUserMapping();
1329+
$id = $mapping->getId();
1330+
1331+
// if mapping is about user
1332+
if ($mapping->getType() === 'user' && !in_array($id, $usersToCheck)) {
1333+
$usersToCheck[] = $id;
1334+
}
1335+
1336+
// if mapping is about group
1337+
if ($mapping->getType() === 'group'
1338+
&& !in_array($mapping->getId(), $knownGroupRules)) {
1339+
$knownGroupRules[] = $mapping->getId();
1340+
1341+
$group = $this->groupManager->get($id);
1342+
if ($group === null) {
1343+
continue;
1344+
}
1345+
$userIds = array_map(function (IUser $user): string {
1346+
return $user->getUID();
1347+
}, $group->getUsers());
1348+
1349+
// merge current user list with members of the group
1350+
$usersToCheck = array_values(array_unique(array_merge($usersToCheck, $userIds)));
1351+
}
1352+
}
1353+
1354+
// now that we have a list of eventuals filtered users, we confirm they have no access to the file
1355+
$filteredUsers = [];
1356+
foreach ($usersToCheck as $userId) {
1357+
try {
1358+
$node = $this->rootFolder->get($cachedPath[$userId]);
1359+
if ($node->isReadable()) {
1360+
continue; // overkill ? as rootFolder->get() would throw an exception if file is not available
1361+
}
1362+
} catch (\Exception $e) {
1363+
}
1364+
1365+
$filteredUsers[] = $userId;
1366+
}
1367+
1368+
return $filteredUsers;
1369+
}
12221370
}

0 commit comments

Comments
 (0)