diff --git a/docs/settings.md b/docs/settings.md index e95738b25a3..b9e402483fa 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -112,6 +112,7 @@ Legend: | `federation_only_trusted_servers` | string
`1` or `0` | `0` | Yes | | 🏗️ *Work in progress:* Whether federation should be limited to the list of "Trusted servers" | | `conversations_files` | string
`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
`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) | +| `delete_one_to_one_conversations` | string
`1` or `0` | `0` | No | ️ | Whether one-to-one conversations can be left by either participant or should be deleted when one participant leaves | | `enable_matterbridge` | string
`1` or `0` | `0` | No | 🖌️ | Whether the Matterbridge integration is enabled and can be configured | | `force_passwords` | string
`1` or `0` | `0` | No | ️ | Whether public chats are forced to use a password | | `inactivity_lock_after_days` | int | `0` | No | | A duration (in days) after which rooms are locked. Calculated from the last activity in the room. | diff --git a/lib/Controller/ChatController.php b/lib/Controller/ChatController.php index 6645ebd09d1..9ae1227d1b3 100644 --- a/lib/Controller/ChatController.php +++ b/lib/Controller/ChatController.php @@ -1185,10 +1185,15 @@ protected function validateMessageExists(int $messageId, bool $sync = false): vo #[RequireReadWriteConversation] public function clearHistory(): DataResponse { $attendee = $this->participant->getAttendee(); - if (!$this->participant->hasModeratorPermissions(false) - || $this->room->getType() === Room::TYPE_ONE_TO_ONE - || $this->room->getType() === Room::TYPE_ONE_TO_ONE_FORMER) { - // Actor is not a moderator or not the owner of the message + if (!$this->participant->hasModeratorPermissions(false)) { + // Actor is not a moderator + return new DataResponse(null, Http::STATUS_FORBIDDEN); + } + + if (!$this->appConfig->getAppValueBool('delete_one_to_one_conversations') + && ($this->room->getType() === Room::TYPE_ONE_TO_ONE + || $this->room->getType() === Room::TYPE_ONE_TO_ONE_FORMER)) { + // Not allowed to purge one-to-one conversations return new DataResponse(null, Http::STATUS_FORBIDDEN); } diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index 70b353c7b06..382fc7f83fe 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -70,6 +70,7 @@ use OCP\AppFramework\Http\Attribute\OpenAPI; use OCP\AppFramework\Http\Attribute\PublicPage; use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Services\IAppConfig; use OCP\AppFramework\Utility\ITimeFactory; use OCP\EventDispatcher\IEventDispatcher; use OCP\Federation\ICloudIdManager; @@ -116,6 +117,7 @@ public function __construct( protected ChecksumVerificationService $checksumVerificationService, protected RoomFormatter $roomFormatter, protected IConfig $config, + protected IAppConfig $appConfig, protected Config $talkConfig, protected ICloudIdManager $cloudIdManager, protected IPhoneNumberUtil $phoneNumberUtil, @@ -857,7 +859,8 @@ public function setDescription(string $description): DataResponse { #[PublicPage] #[RequireModeratorParticipant] public function deleteRoom(): DataResponse { - if ($this->room->getType() === Room::TYPE_ONE_TO_ONE || $this->room->getType() === Room::TYPE_ONE_TO_ONE_FORMER) { + if (!$this->appConfig->getAppValueBool('delete_one_to_one_conversations') + && in_array($this->room->getType(), [Room::TYPE_ONE_TO_ONE, Room::TYPE_ONE_TO_ONE_FORMER], true)) { return new DataResponse(null, Http::STATUS_BAD_REQUEST); } @@ -1375,7 +1378,7 @@ protected function removeSelfFromRoomLogic(Room $room, Participant $participant) } if ($room->getType() !== Room::TYPE_CHANGELOG && - $room->getObjectType() !== 'file' && + $room->getObjectType() !== Room::OBJECT_TYPE_FILE && $this->participantService->getNumberOfUsers($room) === 1 && \in_array($participant->getAttendee()->getParticipantType(), [ Participant::USER, @@ -1386,6 +1389,12 @@ protected function removeSelfFromRoomLogic(Room $room, Participant $participant) return new DataResponse(null); } + if ($this->appConfig->getAppValueBool('delete_one_to_one_conversations') + && in_array($this->room->getType(), [Room::TYPE_ONE_TO_ONE, Room::TYPE_ONE_TO_ONE_FORMER], true)) { + $this->roomService->deleteRoom($room); + return new DataResponse(null); + } + $currentUser = $this->userManager->get($this->userId); if (!$currentUser instanceof IUser) { return new DataResponse(['error' => 'participant'], Http::STATUS_NOT_FOUND); diff --git a/lib/Service/RoomFormatter.php b/lib/Service/RoomFormatter.php index 22fcff9852d..bf1ea0ad7a5 100644 --- a/lib/Service/RoomFormatter.php +++ b/lib/Service/RoomFormatter.php @@ -20,6 +20,7 @@ use OCA\Talk\Webinary; use OCP\App\IAppManager; use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Services\IAppConfig; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Comments\IComment; use OCP\IConfig; @@ -36,6 +37,7 @@ class RoomFormatter { public function __construct( protected Config $talkConfig, + protected IAppConfig $appConfig, protected AvatarService $avatarService, protected ParticipantService $participantService, protected ChatManager $chatManager, @@ -320,6 +322,13 @@ public function formatRoomV4( && $room->getType() !== Room::TYPE_ONE_TO_ONE_FORMER && $currentParticipant->hasModeratorPermissions(false); $roomData['canLeaveConversation'] = $room->getType() !== Room::TYPE_NOTE_TO_SELF; + + if ($this->appConfig->getAppValueBool('delete_one_to_one_conversations') + && in_array($room->getType(), [Room::TYPE_ONE_TO_ONE, Room::TYPE_ONE_TO_ONE_FORMER], true)) { + $roomData['canDeleteConversation'] = true; + $roomData['canLeaveConversation'] = false; + } + $roomData['canEnableSIP'] = $this->talkConfig->isSIPConfigured() && !preg_match(Room::SIP_INCOMPATIBLE_REGEX, $room->getToken()) diff --git a/tests/integration/features/chat-1/delete.feature b/tests/integration/features/chat-1/delete.feature index 4c3cd8d36e8..a50baa9307a 100644 --- a/tests/integration/features/chat-1/delete.feature +++ b/tests/integration/features/chat-1/delete.feature @@ -235,3 +235,13 @@ Feature: chat/delete Then user "participant2" sees the following system messages in room "room1" with 200 (v1) | room | actorType | actorId | actorDisplayName | systemMessage | | room1 | users | participant1 | participant1-displayname | history_cleared | + + Scenario: Can delete chat history in one-to-one conversations when config is set + Given user "participant1" creates room "room" with 201 (v4) + | roomType | 1 | + | invite | participant2 | + And user "participant1" sends message "Message" to room "room" with 201 + Then user "participant1" deletes chat history for room "room" with 403 + When the following "spreed" app config is set + | delete_one_to_one_conversations | 1 | + Then user "participant1" deletes chat history for room "room" with 200 diff --git a/tests/integration/features/conversation-3/one-to-one.feature b/tests/integration/features/conversation-3/one-to-one.feature index b053ad93d8d..996b96d9687 100644 --- a/tests/integration/features/conversation-3/one-to-one.feature +++ b/tests/integration/features/conversation-3/one-to-one.feature @@ -198,7 +198,7 @@ Feature: conversation-2/one-to-one | users | participant1 | 1 | | users | participant2 | 1 | - Scenario: Check share restrictions on one to one conversatio + Scenario: Check share restrictions on one to one conversation Given the following "core" app config is set | shareapi_restrict_user_enumeration_full_match | no | | shareapi_allow_share_dialog_user_enumeration | yes | @@ -207,3 +207,33 @@ Feature: conversation-2/one-to-one And user "participant1" creates room "room15" with 403 (v4) | roomType | 1 | | invite | participant2 | + + Scenario: Remove self from one-to-one conversations when deletable config is set deletes it + Given user "participant1" creates room "room" with 201 (v4) + | roomType | 1 | + | invite | participant2 | + Then user "participant1" removes themselves from room "room" with 200 (v4) + And user "participant1" is participant of the following rooms (v4) + And user "participant2" is participant of the following rooms (v4) + | id | type | participantType | + | room | 1 | 1 | + When user "participant1" creates room "room" with 200 (v4) + | roomType | 1 | + | invite | participant2 | + And the following "spreed" app config is set + | delete_one_to_one_conversations | 1 | + Then user "participant1" removes themselves from room "room" with 200 (v4) + And user "participant1" is participant of the following rooms (v4) + And user "participant2" is participant of the following rooms (v4) + + Scenario: Deleting one-to-one conversations is possible when deletable config is set + Given user "participant1" creates room "room" with 201 (v4) + | roomType | 1 | + | invite | participant2 | + And user "participant1" sends message "Message" to room "room" with 201 + Then user "participant1" deletes room "room" with 400 (v4) + When the following "spreed" app config is set + | delete_one_to_one_conversations | 1 | + Then user "participant1" deletes room "room" with 200 (v4) + And user "participant1" is participant of the following rooms (v4) + And user "participant2" is participant of the following rooms (v4)