Skip to content

Commit 60638ba

Browse files
committed
feat: emit events when users are added or removed from shares
Signed-off-by: Robin Appelman <[email protected]>
1 parent 44721b2 commit 60638ba

13 files changed

+432
-13
lines changed

apps/files_sharing/composer/composer/autoload_classmap.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,15 @@
6363
'OCA\\Files_Sharing\\Listener\\BeforeDirectFileDownloadListener' => $baseDir . '/../lib/Listener/BeforeDirectFileDownloadListener.php',
6464
'OCA\\Files_Sharing\\Listener\\BeforeNodeReadListener' => $baseDir . '/../lib/Listener/BeforeNodeReadListener.php',
6565
'OCA\\Files_Sharing\\Listener\\BeforeZipCreatedListener' => $baseDir . '/../lib/Listener/BeforeZipCreatedListener.php',
66+
'OCA\\Files_Sharing\\Listener\\CircleListenerBase' => $baseDir . '/../lib/Listener/CircleListenerBase.php',
6667
'OCA\\Files_Sharing\\Listener\\LoadAdditionalListener' => $baseDir . '/../lib/Listener/LoadAdditionalListener.php',
6768
'OCA\\Files_Sharing\\Listener\\LoadPublicFileRequestAuthListener' => $baseDir . '/../lib/Listener/LoadPublicFileRequestAuthListener.php',
6869
'OCA\\Files_Sharing\\Listener\\LoadSidebarListener' => $baseDir . '/../lib/Listener/LoadSidebarListener.php',
70+
'OCA\\Files_Sharing\\Listener\\MemberAddedToCircleListener' => $baseDir . '/../lib/Listener/MemberAddedToCircleListener.php',
71+
'OCA\\Files_Sharing\\Listener\\MemberRemovedFromCircleListener' => $baseDir . '/../lib/Listener/MemberRemovedFromCircleListener.php',
6972
'OCA\\Files_Sharing\\Listener\\ShareInteractionListener' => $baseDir . '/../lib/Listener/ShareInteractionListener.php',
7073
'OCA\\Files_Sharing\\Listener\\UserAddedToGroupListener' => $baseDir . '/../lib/Listener/UserAddedToGroupListener.php',
74+
'OCA\\Files_Sharing\\Listener\\UserRemovedFromGroupListener' => $baseDir . '/../lib/Listener/UserRemovedFromGroupListener.php',
7175
'OCA\\Files_Sharing\\Listener\\UserShareAcceptanceListener' => $baseDir . '/../lib/Listener/UserShareAcceptanceListener.php',
7276
'OCA\\Files_Sharing\\Middleware\\OCSShareAPIMiddleware' => $baseDir . '/../lib/Middleware/OCSShareAPIMiddleware.php',
7377
'OCA\\Files_Sharing\\Middleware\\ShareInfoMiddleware' => $baseDir . '/../lib/Middleware/ShareInfoMiddleware.php',

apps/files_sharing/composer/composer/autoload_static.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,15 @@ class ComposerStaticInitFiles_Sharing
7878
'OCA\\Files_Sharing\\Listener\\BeforeDirectFileDownloadListener' => __DIR__ . '/..' . '/../lib/Listener/BeforeDirectFileDownloadListener.php',
7979
'OCA\\Files_Sharing\\Listener\\BeforeNodeReadListener' => __DIR__ . '/..' . '/../lib/Listener/BeforeNodeReadListener.php',
8080
'OCA\\Files_Sharing\\Listener\\BeforeZipCreatedListener' => __DIR__ . '/..' . '/../lib/Listener/BeforeZipCreatedListener.php',
81+
'OCA\\Files_Sharing\\Listener\\CircleListenerBase' => __DIR__ . '/..' . '/../lib/Listener/CircleListenerBase.php',
8182
'OCA\\Files_Sharing\\Listener\\LoadAdditionalListener' => __DIR__ . '/..' . '/../lib/Listener/LoadAdditionalListener.php',
8283
'OCA\\Files_Sharing\\Listener\\LoadPublicFileRequestAuthListener' => __DIR__ . '/..' . '/../lib/Listener/LoadPublicFileRequestAuthListener.php',
8384
'OCA\\Files_Sharing\\Listener\\LoadSidebarListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarListener.php',
85+
'OCA\\Files_Sharing\\Listener\\MemberAddedToCircleListener' => __DIR__ . '/..' . '/../lib/Listener/MemberAddedToCircleListener.php',
86+
'OCA\\Files_Sharing\\Listener\\MemberRemovedFromCircleListener' => __DIR__ . '/..' . '/../lib/Listener/MemberRemovedFromCircleListener.php',
8487
'OCA\\Files_Sharing\\Listener\\ShareInteractionListener' => __DIR__ . '/..' . '/../lib/Listener/ShareInteractionListener.php',
8588
'OCA\\Files_Sharing\\Listener\\UserAddedToGroupListener' => __DIR__ . '/..' . '/../lib/Listener/UserAddedToGroupListener.php',
89+
'OCA\\Files_Sharing\\Listener\\UserRemovedFromGroupListener' => __DIR__ . '/..' . '/../lib/Listener/UserRemovedFromGroupListener.php',
8690
'OCA\\Files_Sharing\\Listener\\UserShareAcceptanceListener' => __DIR__ . '/..' . '/../lib/Listener/UserShareAcceptanceListener.php',
8791
'OCA\\Files_Sharing\\Middleware\\OCSShareAPIMiddleware' => __DIR__ . '/..' . '/../lib/Middleware/OCSShareAPIMiddleware.php',
8892
'OCA\\Files_Sharing\\Middleware\\ShareInfoMiddleware' => __DIR__ . '/..' . '/../lib/Middleware/ShareInfoMiddleware.php',

apps/files_sharing/lib/AppInfo/Application.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
use OC\Group\DisplayNameCache as GroupDisplayNameCache;
1111
use OC\Share\Share;
1212
use OC\User\DisplayNameCache;
13+
use OCA\Circles\Events\CircleMemberAddedEvent;
14+
use OCA\Circles\Events\CircleMemberRemovedEvent;
1315
use OCA\Files\Event\LoadAdditionalScriptsEvent;
1416
use OCA\Files\Event\LoadSidebar;
1517
use OCA\Files_Sharing\Capabilities;
@@ -23,8 +25,11 @@
2325
use OCA\Files_Sharing\Listener\LoadAdditionalListener;
2426
use OCA\Files_Sharing\Listener\LoadPublicFileRequestAuthListener;
2527
use OCA\Files_Sharing\Listener\LoadSidebarListener;
28+
use OCA\Files_Sharing\Listener\MemberAddedToCircleListener;
29+
use OCA\Files_Sharing\Listener\MemberRemovedFromCircleListener;
2630
use OCA\Files_Sharing\Listener\ShareInteractionListener;
2731
use OCA\Files_Sharing\Listener\UserAddedToGroupListener;
32+
use OCA\Files_Sharing\Listener\UserRemovedFromGroupListener;
2833
use OCA\Files_Sharing\Listener\UserShareAcceptanceListener;
2934
use OCA\Files_Sharing\Middleware\OCSShareAPIMiddleware;
3035
use OCA\Files_Sharing\Middleware\ShareInfoMiddleware;
@@ -49,6 +54,7 @@
4954
use OCP\Group\Events\GroupChangedEvent;
5055
use OCP\Group\Events\GroupDeletedEvent;
5156
use OCP\Group\Events\UserAddedEvent;
57+
use OCP\Group\Events\UserRemovedEvent;
5258
use OCP\IDBConnection;
5359
use OCP\IGroup;
5460
use OCP\Share\Events\ShareCreatedEvent;
@@ -97,6 +103,9 @@ function () use ($c) {
97103
$context->registerEventListener(ShareCreatedEvent::class, ShareInteractionListener::class);
98104
$context->registerEventListener(ShareCreatedEvent::class, UserShareAcceptanceListener::class);
99105
$context->registerEventListener(UserAddedEvent::class, UserAddedToGroupListener::class);
106+
$context->registerEventListener(UserRemovedEvent::class, UserRemovedFromGroupListener::class);
107+
$context->registerEventListener(CircleMemberAddedEvent::class, MemberAddedToCircleListener::class);
108+
$context->registerEventListener(CircleMemberRemovedEvent::class, MemberRemovedFromCircleListener::class);
100109

101110
// Publish activity for public download
102111
$context->registerEventListener(BeforeNodeReadEvent::class, BeforeNodeReadListener::class);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2025 Robin Appelman <[email protected]>
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
9+
namespace OCA\Files_Sharing\Listener;
10+
11+
use OC\User\LazyUser;
12+
use OCA\Circles\Model\Circle;
13+
use OCA\Circles\Model\Member;
14+
use OCP\IUser;
15+
use OCP\IUserManager;
16+
use Psr\Log\LoggerInterface;
17+
18+
class CircleListenerBase {
19+
public function __construct(
20+
private readonly IUserManager $userManager,
21+
) {
22+
}
23+
24+
25+
/**
26+
* @return \Iterator<string, IUser>
27+
*/
28+
protected function usersFromMember(Member $member): \Iterator {
29+
if ($member->getUserType() === Member::TYPE_CIRCLE) {
30+
$members = $member->getBasedOn()->getInheritedMembers();
31+
} else {
32+
$members = [$member];
33+
}
34+
35+
foreach ($members as $member) {
36+
if ($member->getUserType() === Member::TYPE_USER) {
37+
yield $member->getUserId() => new LazyUser($member->getUserId(), $this->userManager);
38+
} elseif ($member->getUserType() === Member::TYPE_GROUP) {
39+
// todo
40+
}
41+
}
42+
}
43+
44+
/**
45+
* @return \Iterator<string, IUser>
46+
*/
47+
protected function usersFromCircle(Circle $circle): \Iterator {
48+
foreach ($circle->getInheritedMembers() as $member) {
49+
if ($member->getUserType() === Member::TYPE_USER) {
50+
yield $member->getUserId() => new LazyUser($member->getUserId(), $this->userManager);
51+
} elseif ($member->getUserType() === Member::TYPE_GROUP) {
52+
// todo
53+
}
54+
}
55+
}
56+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2025 Robin Appelman <[email protected]>
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
9+
namespace OCA\Files_Sharing\Listener;
10+
11+
use OCA\Circles\Db\CircleRequest;
12+
use OCA\Circles\Model\Circle;
13+
use OCA\Circles\Model\Member;
14+
use OCA\Circles\Model\Probes\CircleProbe;
15+
16+
class GroupListenerBase {
17+
private function getGroupCircle(CircleRequest $circleRequest, string $groupId): Circle {
18+
$circle = new Circle();
19+
$circle->setName('group:' . $groupId)
20+
->setConfig(Circle::CFG_SYSTEM | Circle::CFG_NO_OWNER | Circle::CFG_HIDDEN)
21+
->setSource(Member::TYPE_GROUP);
22+
return $circleRequest->searchCircle($circle);
23+
}
24+
25+
/**
26+
* @return Circle[]
27+
*/
28+
protected function getCirclesForGroup(CircleRequest $circleRequest, string $groupId): array {
29+
$groupCircle = $this->getGroupCircle($circleRequest, $groupId);
30+
31+
$filterMember = new Member();
32+
$filterMember->setSingleId($groupCircle->getSingleId());
33+
$probe = new CircleProbe();
34+
$probe->filterHiddenCircles()
35+
->filterBackendCircles()
36+
->setFilterMember($filterMember);
37+
return $circleRequest->getCircles(null, $probe);
38+
}
39+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
namespace OCA\Files_Sharing\Listener;
10+
11+
use OCA\Circles\Events\CircleMemberAddedEvent;
12+
use OCP\EventDispatcher\Event;
13+
use OCP\EventDispatcher\IEventDispatcher;
14+
use OCP\EventDispatcher\IEventListener;
15+
use OCP\IUserManager;
16+
use OCP\Share\Events\UserAddedToShareEvent;
17+
use OCP\Share\IManager;
18+
use OCP\Share\IShare;
19+
20+
/** @template-implements IEventListener<CircleMemberAddedEvent> */
21+
class MemberAddedToCircleListener extends CircleListenerBase implements IEventListener {
22+
23+
public function __construct(
24+
private readonly IManager $shareManager,
25+
private readonly IEventDispatcher $eventDispatcher,
26+
private readonly IUserManager $userManager,
27+
) {
28+
parent::__construct($this->userManager);
29+
}
30+
31+
public function handle(Event $event): void {
32+
if (!($event instanceof CircleMemberAddedEvent)) {
33+
return;
34+
}
35+
36+
$users = $this->usersFromMember($event->getMember());
37+
$circle = $event->getCircle();
38+
39+
$shares = null;
40+
foreach ($users as $user) {
41+
if ($shares === null) {
42+
// we only need to get the shares for one user, the shares we're looking for are common between all users
43+
// todo: add a way to get shares by circle id
44+
$shares = $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_CIRCLE, null, -1);
45+
}
46+
47+
foreach ($shares as $share) {
48+
// If this is not the new circle we can skip it
49+
if ($share->getSharedWith() !== $circle->getSingleId()) {
50+
continue;
51+
}
52+
53+
$this->eventDispatcher->dispatchTyped(new UserAddedToShareEvent($share, $user));
54+
}
55+
}
56+
}
57+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
namespace OCA\Files_Sharing\Listener;
10+
11+
use OCA\Circles\Events\CircleMemberRemovedEvent;
12+
use OCP\EventDispatcher\Event;
13+
use OCP\EventDispatcher\IEventDispatcher;
14+
use OCP\EventDispatcher\IEventListener;
15+
use OCP\IUserManager;
16+
use OCP\Share\Events\UserRemovedFromShareEvent;
17+
use OCP\Share\IManager;
18+
use OCP\Share\IShare;
19+
20+
/** @template-implements IEventListener<CircleMemberRemovedEvent> */
21+
class MemberRemovedFromCircleListener extends CircleListenerBase implements IEventListener {
22+
23+
public function __construct(
24+
private readonly IManager $shareManager,
25+
private readonly IEventDispatcher $eventDispatcher,
26+
private readonly IUserManager $userManager,
27+
) {
28+
parent::__construct($this->userManager);
29+
}
30+
31+
public function handle(Event $event): void {
32+
if (!($event instanceof CircleMemberRemovedEvent)) {
33+
return;
34+
}
35+
36+
$circle = $event->getCircle();
37+
38+
$circleMembers = iterator_to_array($this->usersFromCircle($circle));
39+
// todo: add a way to get shares by circle id
40+
if (count($circleMembers)) {
41+
$user = current($circleMembers);
42+
// get the shares from a user still in the circle
43+
$shares = $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_CIRCLE, null, -1);
44+
} else {
45+
// if nobody is in the circle anymore we current have to go through all shares
46+
// todo: add a way to get shares by group id
47+
$shares = $this->shareManager->getAllShares();
48+
}
49+
50+
foreach ($shares as $share) {
51+
// If this is not the new group we can skip it
52+
if ($share->getShareType() === IShare::TYPE_CIRCLE && $share->getSharedWith() !== $circle->getSingleId()) {
53+
continue;
54+
}
55+
56+
foreach ($this->usersFromMember($event->getMember()) as $user) {
57+
if (!isset($circleMembers[$user->getUID()])) {
58+
$this->eventDispatcher->dispatchTyped(new UserRemovedFromShareEvent($share, $user));
59+
}
60+
}
61+
}
62+
}
63+
}

apps/files_sharing/lib/Listener/UserAddedToGroupListener.php

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,31 @@
66
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
77
* SPDX-License-Identifier: AGPL-3.0-or-later
88
*/
9+
910
namespace OCA\Files_Sharing\Listener;
1011

12+
use OCA\Circles\Db\CircleRequest;
13+
use OCA\Circles\Model\Circle;
1114
use OCA\Files_Sharing\AppInfo\Application;
15+
use OCP\App\IAppManager;
1216
use OCP\EventDispatcher\Event;
17+
use OCP\EventDispatcher\IEventDispatcher;
1318
use OCP\EventDispatcher\IEventListener;
1419
use OCP\Group\Events\UserAddedEvent;
1520
use OCP\IConfig;
21+
use OCP\Share\Events\UserAddedToShareEvent;
1622
use OCP\Share\IManager;
1723
use OCP\Share\IShare;
24+
use Psr\Container\ContainerInterface;
1825

1926
/** @template-implements IEventListener<UserAddedEvent> */
20-
class UserAddedToGroupListener implements IEventListener {
21-
27+
class UserAddedToGroupListener extends GroupListenerBase implements IEventListener {
2228
public function __construct(
23-
private IManager $shareManager,
24-
private IConfig $config,
29+
private readonly IManager $shareManager,
30+
private readonly IConfig $config,
31+
private readonly IEventDispatcher $eventDispatcher,
32+
private readonly IAppManager $appManager,
33+
private readonly ContainerInterface $container,
2534
) {
2635
}
2736

@@ -33,22 +42,44 @@ public function handle(Event $event): void {
3342
$user = $event->getUser();
3443
$group = $event->getGroup();
3544

36-
// This user doesn't have autoaccept so we can skip it all
37-
if (!$this->hasAutoAccept($user->getUID())) {
38-
return;
39-
}
45+
// Get all group shares this user has access too now to filter later
46+
$groupShares = $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_GROUP, null, -1);
4047

41-
// Get all group shares this user has access to now to filter later
42-
$shares = $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_GROUP, null, -1);
43-
44-
foreach ($shares as $share) {
48+
foreach ($groupShares as $share) {
4549
// If this is not the new group we can skip it
4650
if ($share->getSharedWith() !== $group->getGID()) {
4751
continue;
4852
}
4953

5054
// Accept the share if needed
51-
$this->shareManager->acceptShare($share, $user->getUID());
55+
if ($this->hasAutoAccept($user->getUID())) {
56+
$this->shareManager->acceptShare($share, $user->getUID());
57+
}
58+
59+
$this->eventDispatcher->dispatchTyped(new UserAddedToShareEvent($share, $user));
60+
}
61+
62+
if ($this->appManager->isAppLoaded('circles')) {
63+
// Get all circle shares this user has access too now to filter later
64+
$circleShares = $this->shareManager->getSharedWith($user->getUID(), IShare::TYPE_CIRCLE, null, -1);
65+
if (count($circleShares) === 0) {
66+
return;
67+
}
68+
69+
/** @var CircleRequest $circlesRequest */
70+
$circlesRequest = $this->container->get(CircleRequest::class);
71+
$circles = $this->getCirclesForGroup($circlesRequest, $group->getGID());
72+
$circleIds = array_map(fn (Circle $circle) => $circle->getSingleId(), $circles);
73+
74+
foreach ($circleShares as $share) {
75+
if (!in_array($share->getSharedWith(), $circleIds)) {
76+
continue;
77+
}
78+
// todo: detect if this share is new for the user because it only has access trough the new group
79+
// or if the user already had access before
80+
81+
$this->eventDispatcher->dispatchTyped(new UserAddedToShareEvent($share, $user));
82+
}
5283
}
5384
}
5485

0 commit comments

Comments
 (0)