Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command to transfer ownership #8479

Merged
merged 4 commits into from Jan 3, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions appinfo/info.xml
Expand Up @@ -105,6 +105,7 @@ And in the works for the [coming versions](https://github.com/nextcloud/spreed/m
<command>OCA\Talk\Command\Room\Demote</command>
<command>OCA\Talk\Command\Room\Update</command>
<command>OCA\Talk\Command\User\Remove</command>
<command>OCA\Talk\Command\User\TransferOwnership</command>
</commands>

<settings>
Expand Down
175 changes: 175 additions & 0 deletions lib/Command/User/TransferOwnership.php
@@ -0,0 +1,175 @@
<?php

declare(strict_types=1);
/**
* @copyright Copyright (c) 2021, Vitor Mattos <vitor@php.rio>
*
* @author Vitor Mattos <vitor@php.rio>
nickvergessen marked this conversation as resolved.
Show resolved Hide resolved
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\Talk\Command\User;

use OC\Core\Command\Base;
use OCA\Talk\Exceptions\ParticipantNotFoundException;
use OCA\Talk\Manager;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\RoomService;
use OCP\IUserManager;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class TransferOwnership extends Base {
private RoomService $roomService;
private ParticipantService $participantService;
private Manager $manager;
private IUserManager $userManager;

public function __construct(ParticipantService $participantService,
Manager $manager,
IUserManager $userManager) {
parent::__construct();
$this->participantService = $participantService;
$this->manager = $manager;
$this->userManager = $userManager;
}

protected function configure(): void {
$this
->setName('talk:user:transfer-ownership')
->setDescription('Adds the destination-user with the same participant type to all (not one-to-one) conversations of source-user')
->addArgument(
'source-user',
InputArgument::REQUIRED,
'Owner of conversations which shall be moved'
)
->addArgument(
'destination-user',
InputArgument::REQUIRED,
'User who will be the new owner of the conversations'
)
->addOption(
'include-non-moderator',
null,
InputOption::VALUE_NONE,
'Also include conversations where the source-user is a normal user'
)
->addOption(
'remove-source-user',
null,
InputOption::VALUE_NONE,
'Remove the source-user from the conversations'
)
;
}

protected function execute(InputInterface $input, OutputInterface $output): int {
$sourceUID = $input->getArgument('source-user');
$destinationUID = $input->getArgument('destination-user');

$destinationUser = $this->userManager->get($destinationUID);
if ($destinationUser === null) {
$output->writeln('<error>Destination user could not be found.</error>');
return 1;
}

$includeNonModeratorRooms = $input->getOption('include-non-moderator');
$removeSourceUser = $input->getOption('remove-source-user');

$modified = 0;
$rooms = $this->manager->getRoomsForActor(Attendee::ACTOR_USERS, $sourceUID);
foreach ($rooms as $room) {
if ($room->getType() !== Room::TYPE_GROUP && $room->getType() !== Room::TYPE_PUBLIC) {
// Skip one-to-one, changelog and any other room types
continue;
}

$sourceParticipant = $this->participantService->getParticipantByActor($room, Attendee::ACTOR_USERS, $sourceUID);

if ($sourceParticipant->getAttendee()->getParticipantType() === Participant::USER_SELF_JOINED) {
continue;
}

if (!$includeNonModeratorRooms && !$sourceParticipant->hasModeratorPermissions()) {
continue;
}

try {
$destinationParticipant = $this->participantService->getParticipantByActor($room, Attendee::ACTOR_USERS, $destinationUser->getUID());

$targetType = $this->shouldUpdateParticipantType($sourceParticipant->getAttendee()->getParticipantType(), $destinationParticipant->getAttendee()->getParticipantType());

if ($targetType !== null) {
$this->participantService->updateParticipantType(
$room,
$destinationParticipant,
$sourceParticipant->getAttendee()->getParticipantType()
);
$modified++;
}
} catch (ParticipantNotFoundException $e) {
$this->participantService->addUsers($room, [
[
'actorType' => Attendee::ACTOR_USERS,
'actorId' => $destinationUser->getUID(),
'displayName' => $destinationUser->getDisplayName(),
'participantType' => $sourceParticipant->getAttendee()->getParticipantType(),
]
]);
$modified++;
}

if ($removeSourceUser) {
$this->participantService->removeAttendee($room, $sourceParticipant, Room::PARTICIPANT_REMOVED);
}
}

$output->writeln('<info>Added or promoted user ' . $destinationUser->getUID() . ' in ' . $modified . ' rooms.</info>');
return 0;
}

protected function shouldUpdateParticipantType(int $sourceParticipantType, int $destinationParticipantType): ?int {
if ($sourceParticipantType === Participant::OWNER) {
if ($destinationParticipantType === Participant::OWNER) {
return null;
}
return $sourceParticipantType;
}

if ($sourceParticipantType === Participant::MODERATOR) {
if ($destinationParticipantType === Participant::OWNER || $destinationParticipantType === Participant::MODERATOR) {
return null;
}
return $sourceParticipantType;
}

if ($sourceParticipantType === Participant::USER) {
if ($destinationParticipantType !== Participant::USER_SELF_JOINED) {
return null;
}
return $sourceParticipantType;
}

return null;
}
}
3 changes: 3 additions & 0 deletions lib/Service/ParticipantService.php
Expand Up @@ -134,6 +134,9 @@ public function updateParticipantType(Room $room, Participant $participant, int
}

$oldType = $attendee->getParticipantType();
if ($oldType === $participantType) {
return;
}

$event = new ModifyParticipantEvent($room, $participant, 'type', $participantType, $oldType);
$this->dispatcher->dispatch(Room::EVENT_BEFORE_PARTICIPANT_TYPE_SET, $event);
Expand Down
14 changes: 13 additions & 1 deletion tests/integration/features/bootstrap/FeatureContext.php
Expand Up @@ -40,6 +40,8 @@ class FeatureContext implements Context, SnippetAcceptingContext {
/** @var string[] */
protected static $identifierToToken;
/** @var string[] */
protected static $identifierToId;
/** @var string[] */
protected static $tokenToIdentifier;
/** @var array[] */
protected static $identifierToAvatar;
Expand Down Expand Up @@ -146,6 +148,7 @@ public function __construct() {
*/
public function setUp() {
self::$identifierToToken = [];
self::$identifierToId = [];
self::$tokenToIdentifier = [];
self::$sessionIdToUser = [];
self::$userToSessionId = [];
Expand Down Expand Up @@ -325,7 +328,15 @@ private function assertRooms($rooms, TableNode $formData, bool $shouldOrder = fa
$expected = $formData->getHash();
if ($shouldOrder) {
$sorter = static function (array $roomA, array $roomB): int {
return $roomA['id'] < $roomB['id'] ? -1 : 1;
$idA = $roomA['id'];
$idB = $roomB['id'];
if (isset(self::$identifierToId[$idA])) {
$idA = self::$identifierToId[$idA];
}
if (isset(self::$identifierToId[$idB])) {
$idB = self::$identifierToId[$idB];
}
return $idA < $idB ? -1 : 1;
};

usort($expected, $sorter);
Expand Down Expand Up @@ -786,6 +797,7 @@ public function userCreatesRoomWith(string $user, string $identifier, int $statu

if ($statusCode === 201) {
self::$identifierToToken[$identifier] = $response['token'];
self::$identifierToId[$identifier] = $response['id'];
self::$tokenToIdentifier[$response['token']] = $identifier;
}
}
Expand Down