Skip to content

Commit

Permalink
feat(rooms): add option to automatically lock public and group rooms …
Browse files Browse the repository at this point in the history
…if they are inactive

Signed-off-by: Anna Larch <anna@nextcloud.com>
  • Loading branch information
miaulalala committed Nov 22, 2024
1 parent df0efdb commit f11783e
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 1 deletion.
3 changes: 2 additions & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* 🌉 **Sync with other chat solutions** With [Matterbridge](https://github.com/42wim/matterbridge/) being integrated in Talk, you can easily sync a lot of other chat solutions to Nextcloud Talk and vice-versa.
]]></description>

<version>21.0.0-dev.2</version>
<version>21.0.0-dev.3</version>
<licence>agpl</licence>

<author>Anna Larch</author>
Expand Down Expand Up @@ -66,6 +66,7 @@
<job>OCA\Talk\BackgroundJob\CheckTurnCertificate</job>
<job>OCA\Talk\BackgroundJob\ExpireChatMessages</job>
<job>OCA\Talk\BackgroundJob\ExpireSignalingMessage</job>
<job>OCA\Talk\BackgroundJob\LockInactiveRooms</job>
<job>OCA\Talk\BackgroundJob\MaximumCallDuration</job>
<job>OCA\Talk\BackgroundJob\Reminder</job>
<job>OCA\Talk\BackgroundJob\RemoveEmptyRooms</job>
Expand Down
2 changes: 2 additions & 0 deletions docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,5 @@ Legend:
| `conversations_files` | string<br>`1` or `0` | `1` | No | 🖌️ | Whether the files app integration is enabled allowing to start conversations in the right sidebar |
| `conversations_files_public_shares` | string<br>`1` or `0` | `1` | No | 🖌️ | Whether the public share integration is enabled allowing to start conversations in the right sidebar on the public share page (Requires `conversations_files` also to be enabled) |
| `enable_matterbridge` | string<br>`1` or `0` | `0` | No | 🖌️ | Whether the Matterbridge integration is enabled and can be configured |
| `inactivity_lock_after_days` | int | `0` | No | | A duration (in days) after which rooms are locked. Calculated from the last activity in the room. |
| `inactivity_enable_lobby` | string<br>`1` or `0` | `0` | No | | Additionally enable the lobby for inactive rooms so they can only be read by moderators. |
56 changes: 56 additions & 0 deletions lib/BackgroundJob/LockInactiveRooms.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Talk\BackgroundJob;

use OCA\Talk\Config;
use OCA\Talk\Room;
use OCA\Talk\Service\RoomService;
use OCA\Talk\Webinary;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJob;
use OCP\BackgroundJob\TimedJob;
use Psr\Log\LoggerInterface;

class LockInactiveRooms extends TimedJob {

public function __construct(
ITimeFactory $timeFactory,
private RoomService $roomService,
private Config $appConfig,
private LoggerInterface $logger,
) {
parent::__construct($timeFactory);

// Every hour
$this->setInterval(60 * 60 * 24);
$this->setTimeSensitivity(IJob::TIME_SENSITIVE);
}

/**
* @inheritDoc
*/
public function run($argument): void {
$interval = $this->appConfig->getInactiveLockTime();
$forceLobby = $this->appConfig->enableLobbyOnLockedRooms();
if ($interval === 0) {
return;
}
$timestamp = $this->time->getTime() - $interval * 60 * 60 * 24;
$time = $this->time->getDateTime('@' . $timestamp);
$rooms = $this->roomService->getInactiveRooms($time);
array_map(function (Room $room) use ($forceLobby) {
$this->roomService->setReadOnly($room, Room::READ_ONLY);
$this->logger->debug("Locking room {$room->getId()} due to inactivity");
if ($forceLobby) {
$this->roomService->setLobby($room, Webinary::LOBBY_NON_MODERATORS, $this->time->getDateTime());
$this->logger->debug("Enabling lobby for room {$room->getId()}");
}
}, $rooms);
}
}
11 changes: 11 additions & 0 deletions lib/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -694,4 +694,15 @@ public function getBlurVirtualBackground(?string $userId): bool {
}
return false;
}

/**
* User setting falling back to admin defined app config
*/
public function getInactiveLockTime(): int {
return $this->appConfig->getAppValueInt('inactivity_lock_after_days');
}

public function enableLobbyOnLockedRooms(): bool {
return $this->appConfig->getAppValueBool('inactivity_enable_lobby');
}
}
24 changes: 24 additions & 0 deletions lib/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,30 @@ public function getRoomsLongerActiveSince(\DateTime $maxActiveSince): array {
return $rooms;
}

/**
* @return list<Room>
*/
public function getInactiveRooms(\DateTime $inactiveSince): array {
$query = $this->db->getQueryBuilder();
$helper = new SelectHelper();
$helper->selectRoomsTable($query);
$query->from('talk_rooms', 'r')
->andWhere($query->expr()->lte('r.last_activity', $query->createNamedParameter($inactiveSince, IQueryBuilder::PARAM_DATETIME_MUTABLE)))
->andWhere($query->expr()->neq('r.read_only', $query->createNamedParameter(Room::READ_ONLY, IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->in('r.type', $query->createNamedParameter([Room::TYPE_PUBLIC, Room::TYPE_GROUP], IQueryBuilder::PARAM_INT_ARRAY)))
->andWhere($query->expr()->emptyString('remoteServer'))
->orderBy('r.id', 'ASC');
$result = $query->executeQuery();

$rooms = [];
while ($row = $result->fetch()) {
$rooms[] = $this->createRoomObject($row);
}
$result->closeCursor();

return $rooms;
}

/**
* @param string $userId
* @param array $sessionIds A list of talk sessions to consider for loading (otherwise no session is loaded)
Expand Down
7 changes: 7 additions & 0 deletions lib/Service/RoomService.php
Original file line number Diff line number Diff line change
Expand Up @@ -1212,4 +1212,11 @@ public function deleteRoom(Room $room): void {
));
}
}

/**
* @return list<Room>
*/
public function getInactiveRooms(\DateTime $inactiveSince): array {
return $this->manager->getInactiveRooms($inactiveSince);
}
}
153 changes: 153 additions & 0 deletions tests/php/BackgroundJob/LockInactiveRoomsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Talk\Tests\BackgroundJob;

use OCA\Talk\BackgroundJob\LockInactiveRooms;
use OCA\Talk\Config;
use OCA\Talk\Room;
use OCA\Talk\Service\RoomService;
use OCP\AppFramework\Utility\ITimeFactory;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Test\TestCase;

class LockInactiveRoomsTest extends TestCase {
protected ITimeFactory&MockObject $timeFactory;
protected RoomService&MockObject $roomService;
private Config&MockObject $appConfig;
protected LoggerInterface&MockObject $logger;
private LockInactiveRooms $job;

public function setUp(): void {
parent::setUp();

$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->roomService = $this->createMock(RoomService::class);
$this->appConfig = $this->createMock(Config::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->job = new LockInactiveRooms($this->timeFactory,
$this->roomService,
$this->appConfig,
$this->logger
);
}

public function testNotEnabled(): void {
$this->appConfig->expects(self::once())
->method('getInactiveLockTime')
->willReturn(0);
$this->appConfig->expects(self::once())
->method('enableLobbyOnLockedRooms')
->willReturn(false);
$this->timeFactory->expects(self::never())
->method(self::anything());
$this->roomService->expects(self::never())
->method(self::anything());
$this->logger->expects(self::never())
->method(self::anything());

$this->job->run('t');
}

public function testNoRooms(): void {
$this->appConfig->expects(self::once())
->method('getInactiveLockTime')
->willReturn(123);
$this->appConfig->expects(self::once())
->method('enableLobbyOnLockedRooms')
->willReturn(false);
$this->timeFactory->expects(self::once())
->method('getTime');
$this->timeFactory->expects(self::once())
->method('getDateTime');
$this->roomService->expects(self::once())
->method('getInactiveRooms')
->willReturn([]);
$this->roomService->expects(self::never())
->method('setReadOnly');
$this->roomService->expects(self::never())
->method('setLobby');
$this->logger->expects(self::never())
->method(self::anything());

$this->job->run('t');

}

public function testLockRooms(): void {
$rooms = [
$this->createConfiguredMock(Room::class, [
'getReadOnly' => 0,
'getType' => Room::TYPE_PUBLIC,
]),
$this->createConfiguredMock(Room::class, [
'getReadOnly' => 0,
'getType' => Room::TYPE_GROUP,
]),
];

$this->appConfig->expects(self::once())
->method('getInactiveLockTime')
->willReturn(123);
$this->appConfig->expects(self::once())
->method('enableLobbyOnLockedRooms')
->willReturn(false);
$this->timeFactory->expects(self::once())
->method('getTime');
$this->timeFactory->expects(self::once())
->method('getDateTime');
$this->roomService->expects(self::once())
->method('getInactiveRooms')
->willReturn($rooms);
$this->roomService->expects(self::exactly(2))
->method('setReadOnly');
$this->roomService->expects(self::never())
->method('setLobby');
$this->logger->expects(self::exactly(2))
->method('debug');

$this->job->run('t');

}

public function testLockRoomsAndEnableLobby(): void {
$rooms = [
$this->createConfiguredMock(Room::class, [
'getReadOnly' => 0,
'getType' => Room::TYPE_PUBLIC,
]),
$this->createConfiguredMock(Room::class, [
'getReadOnly' => 0,
'getType' => Room::TYPE_GROUP,
]),
];

$this->appConfig->expects(self::once())
->method('getInactiveLockTime')
->willReturn(123);
$this->appConfig->expects(self::once())
->method('enableLobbyOnLockedRooms')
->willReturn(true);
$this->timeFactory->expects(self::once())
->method('getTime');
$this->timeFactory->expects(self::any())
->method('getDateTime');
$this->roomService->expects(self::once())
->method('getInactiveRooms')
->willReturn($rooms);
$this->roomService->expects(self::exactly(2))
->method('setReadOnly');
$this->roomService->expects(self::exactly(2))
->method('setLobby');
$this->logger->expects(self::exactly(4))
->method('debug');

$this->job->run('t');
}
}

0 comments on commit f11783e

Please sign in to comment.