Skip to content

Commit beaf020

Browse files
committed
groupfolders+acl
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
1 parent 14f175f commit beaf020

File tree

1 file changed

+163
-13
lines changed

1 file changed

+163
-13
lines changed

lib/FilesHooks.php

Lines changed: 163 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,160 @@ 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+
/** @var \OCA\GroupFolders\Folder\FolderManager $folderManager */
1254+
try {
1255+
$ruleManager = \OC::$server->get(\OCA\GroupFolders\ACL\RuleManager::class);
1256+
$folderManager = \OC::$server->get(\OCA\GroupFolders\Folder\FolderManager::class);
1257+
} catch (\Exception $e) {
1258+
return []; // if we have no access to RuleManager, we cannot filter unrelated users
1259+
}
1260+
1261+
/** @var \OCA\GroupFolders\ACL\Rule[] $rules */
1262+
$rules = $knownRules = $knownGroupRules = $usersToCheck = $cachedPath = [];
1263+
foreach ($cachedMounts as $cachedMount) {
1264+
1265+
// caching rules based on storage id
1266+
$storageId = $cachedMount['storageId'];
1267+
if (!array_key_exists($storageId, $knownRules)) {
1268+
$knownRules[$storageId] = [];
1269+
}
1270+
1271+
$cachedPath[$cachedMount['userId']] = $fullPath = $cachedMount['path'];
1272+
1273+
// caching rules based on storage+path to file
1274+
if (!array_key_exists($cachedMount['visiblePath'], $knownRules[$storageId])) {
1275+
// we need mountPoint and folderId to generate the correct path
1276+
try {
1277+
$node = $this->rootFolder->get($fullPath);
1278+
$mountPoint = $node->getMountPoint();
1279+
1280+
if (!$mountPoint instanceof \OCA\GroupFolders\Mount\GroupMountPoint) {
1281+
continue;
1282+
}
1283+
1284+
$folderPath = trim($mountPoint->getSourcePath(), '/');
1285+
$path = substr($fullPath, strlen($mountPoint->getMountPoint()));
1286+
} catch (\Exception $e) {
1287+
1288+
// in case of issue during the process, we can imagine the user have no access to the file
1289+
$usersToCheck[] = $cachedMount['userId'];
1290+
continue; // we'll catch rules on next user with access to the file
1291+
}
1292+
1293+
// we generate a list of path from top level of group folder to the file itself to get all rules
1294+
$paths = [$folderPath];
1295+
while ($path !== '') {
1296+
$paths[] = $folderPath . '/' . $path;
1297+
$path = dirname($path);
1298+
if ($path === '.' || $path === '/') {
1299+
$path = '';
1300+
}
1301+
}
1302+
1303+
// we might already know the rules for some path of the list
1304+
$paths = array_filter($paths, function (string $path) use ($knownRules, $storageId): bool {
1305+
if (array_key_exists($path, $knownRules[$storageId])) {
1306+
return false;
1307+
}
1308+
1309+
return true;
1310+
});
1311+
1312+
// we get and store the rules for each path from the list
1313+
$rulesPerPath = $ruleManager->getAllRulesForPaths($storageId, $paths);
1314+
foreach (array_keys($rulesPerPath) as $path) {
1315+
$rules = array_merge($rules, $rulesPerPath[$path]);
1316+
}
1317+
1318+
$knownRules[$storageId][$cachedMount['visiblePath']] = true;
1319+
}
1320+
}
1321+
1322+
// using each rules that disable read permission to generate a list of users
1323+
// that might not have access to fileId
1324+
foreach ($rules as $rule) {
1325+
if (($rule->getMask() & Constants::PERMISSION_READ) === 0
1326+
|| ($rule->getPermissions() & Constants::PERMISSION_READ) !== 0) {
1327+
continue; // not interested of rules with 'mask' not including read capability (1), or if 'permission' does
1328+
}
1329+
1330+
$mapping = $rule->getUserMapping();
1331+
$id = $mapping->getId();
1332+
1333+
// if mapping is about user
1334+
if ($mapping->getType() === 'user' && !in_array($id, $usersToCheck)) {
1335+
$usersToCheck[] = $id;
1336+
}
1337+
1338+
// if mapping is about group
1339+
if ($mapping->getType() === 'group'
1340+
&& !in_array($mapping->getId(), $knownGroupRules)) {
1341+
$knownGroupRules[] = $mapping->getId();
1342+
1343+
$group = $this->groupManager->get($id);
1344+
if ($group === null) {
1345+
continue;
1346+
}
1347+
$userIds = array_map(function (IUser $user): string {
1348+
return $user->getUID();
1349+
}, $group->getUsers());
1350+
1351+
// merge current user list with members of the group
1352+
$usersToCheck = array_values(array_unique(array_merge($usersToCheck, $userIds)));
1353+
}
1354+
}
1355+
1356+
// now that we have a list of eventuals filtered users, we confirm they have no access to the file
1357+
$filteredUsers = [];
1358+
foreach ($usersToCheck as $userId) {
1359+
try {
1360+
$node = $this->rootFolder->get($cachedPath[$userId]);
1361+
if ($node->isReadable()) {
1362+
continue; // overkill ? as rootFolder->get() would throw an exception if file is not available
1363+
}
1364+
} catch (\Exception $e) {
1365+
}
1366+
1367+
$filteredUsers[] = $userId;
1368+
}
1369+
1370+
return $filteredUsers;
1371+
}
12221372
}

0 commit comments

Comments
 (0)