summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShankar Kalidindi <94879708+skalidindi53@users.noreply.github.com>2024-07-10 11:03:55 -0500
committerGitHub <noreply@github.com>2024-07-10 11:03:55 -0500
commit193b70eff9bb867e4611fe38ea06ff7407c4f03c (patch)
tree4c11d334dbd3664fdcff80cb920728048d172f1d
parentaeab37a4827039778d29823aa9685e650388162f (diff)
parent49741ae91aa3c336b1e4f5c05e308aaecf830ff9 (diff)
Merge pull request #12558 from nextcloud/skalidindi53/12292/Banning-users-and-guests
feat: Added database migration for banning users
-rw-r--r--appinfo/info.xml2
-rw-r--r--appinfo/routes/routesBanController.php2
-rw-r--r--lib/Controller/BanController.php74
-rw-r--r--lib/Migration/Version20000Date20240621150333.php70
-rw-r--r--lib/Model/Ban.php68
-rw-r--r--lib/Model/BanMapper.php64
-rw-r--r--lib/Service/BanService.php78
-rw-r--r--openapi-full.json30
-rw-r--r--openapi.json30
-rw-r--r--src/services/banService.ts7
-rw-r--r--src/types/openapi/openapi-full.ts26
-rw-r--r--src/types/openapi/openapi.ts26
-rw-r--r--tests/integration/features/bootstrap/FeatureContext.php117
-rw-r--r--tests/integration/features/conversation-1/ban.feature92
14 files changed, 602 insertions, 84 deletions
diff --git a/appinfo/info.xml b/appinfo/info.xml
index de2513b58..bf4c0e5a0 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -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>20.0.0-dev.2</version>
+ <version>20.0.0-dev.3</version>
<licence>agpl</licence>
<author>Daniel Calviño Sánchez</author>
diff --git a/appinfo/routes/routesBanController.php b/appinfo/routes/routesBanController.php
index d38bb6397..cdda427a8 100644
--- a/appinfo/routes/routesBanController.php
+++ b/appinfo/routes/routesBanController.php
@@ -17,6 +17,6 @@ return [
/** @see \OCA\Talk\Controller\BanController::listBans() */
['name' => 'Ban#listBans', 'url' => '/api/{apiVersion}/ban/{token}', 'verb' => 'GET', 'requirements' => $requirements],
/** @see \OCA\Talk\Controller\BanController::unbanActor() */
- ['name' => 'Ban#unbanActor', 'url' => '/api/{apiVersion}/ban/{token}', 'verb' => 'DELETE', 'requirements' => $requirements],
+ ['name' => 'Ban#unbanActor', 'url' => '/api/{apiVersion}/ban/{token}/{banId}', 'verb' => 'DELETE', 'requirements' => $requirements],
],
];
diff --git a/lib/Controller/BanController.php b/lib/Controller/BanController.php
index ed39a9dd5..43cf0bca1 100644
--- a/lib/Controller/BanController.php
+++ b/lib/Controller/BanController.php
@@ -11,10 +11,13 @@ namespace OCA\Talk\Controller;
use OCA\Talk\Middleware\Attribute\RequireModeratorParticipant;
use OCA\Talk\Model\Attendee;
+use OCA\Talk\Model\Ban;
use OCA\Talk\ResponseDefinitions;
+use OCA\Talk\Service\BanService;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IRequest;
/**
@@ -24,6 +27,8 @@ class BanController extends AEnvironmentAwareController {
public function __construct(
string $appName,
IRequest $request,
+ protected BanService $banService,
+ protected ITimeFactory $timeFactory,
) {
parent::__construct($appName, $request);
}
@@ -37,7 +42,7 @@ class BanController extends AEnvironmentAwareController {
* @psalm-param Attendee::ACTOR_*|'ip' $actorType Type of actor to ban, or `ip` when banning a clients remote address
* @param string $actorId Actor ID or the IP address or range in case of type `ip`
* @param string $internalNote Optional internal note
- * @return DataResponse<Http::STATUS_OK, TalkBan, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array{error: string}, array{}>
+ * @return DataResponse<Http::STATUS_OK, TalkBan, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array{error: 'bannedActor'|'internalNote'}, array{}>
*
* 200: Ban successfully
* 400: Actor information is invalid
@@ -45,25 +50,29 @@ class BanController extends AEnvironmentAwareController {
#[PublicPage]
#[RequireModeratorParticipant]
public function banActor(string $actorType, string $actorId, string $internalNote = ''): DataResponse {
- if ($actorId === 'wrong') {
+ try {
+ $moderator = $this->participant->getAttendee();
+ $moderatorActorType = $moderator->getActorType();
+ $moderatorActorId = $moderator->getActorId();
+
+ $ban = $this->banService->createBan(
+ $moderatorActorId,
+ $moderatorActorType,
+ $this->room->getId(),
+ $actorId,
+ $actorType,
+ $this->timeFactory->getDateTime(),
+ $internalNote
+ );
+
+ return new DataResponse($ban->jsonSerialize(), Http::STATUS_OK);
+ } catch (\InvalidArgumentException $e) {
+ /** @var 'bannedActor'|'internalNote' $message */
+ $message = $e->getMessage();
return new DataResponse([
- 'error' => 'actor',
+ 'error' => $message,
], Http::STATUS_BAD_REQUEST);
}
-
-
- return new DataResponse(
- [
- 'id' => random_int(1, 1337),
- 'actorType' => $this->participant->getAttendee()->getActorType(),
- 'actorId' => $this->participant->getAttendee()->getActorId(),
- 'bannedType' => $actorType,
- 'bannedId' => $actorId,
- 'bannedTime' => time(),
- 'internalNote' => $internalNote ?: 'Lorem ipsum',
- ],
- Http::STATUS_OK
- );
}
/**
@@ -71,23 +80,16 @@ class BanController extends AEnvironmentAwareController {
*
* Required capability: `ban-v1`
*
- * @return DataResponse<Http::STATUS_OK, list<TalkBan>, array{}>
+ * @return DataResponse<Http::STATUS_OK, TalkBan[], array{}>
*
* 200: List all bans
*/
#[PublicPage]
#[RequireModeratorParticipant]
public function listBans(): DataResponse {
- return new DataResponse([
- $this->randomBan(Attendee::ACTOR_USERS, 'test'),
- $this->randomBan(Attendee::ACTOR_USERS, '123456'),
- $this->randomBan(Attendee::ACTOR_FEDERATED_USERS, 'admin@nextcloud.local'),
- $this->randomBan('ip', '127.0.0.1'),
- $this->randomBan('ip', '127.0.0.1/32'),
- $this->randomBan('ip', '127.0.0.0/24'),
- $this->randomBan('ip', '::1/24'),
- $this->randomBan('ip', '2001:0db8:85a3::/48'),
- ], Http::STATUS_OK);
+ $bans = $this->banService->getBansForRoom($this->room->getId());
+ $result = array_map(static fn (Ban $ban): array => $ban->jsonSerialize(), $bans);
+ return new DataResponse($result, Http::STATUS_OK);
}
/**
@@ -103,21 +105,7 @@ class BanController extends AEnvironmentAwareController {
#[PublicPage]
#[RequireModeratorParticipant]
public function unbanActor(int $banId): DataResponse {
+ $this->banService->findAndDeleteBanByIdForRoom($banId, $this->room->getId());
return new DataResponse([], Http::STATUS_OK);
}
-
- /**
- * @psalm-return TalkBan
- */
- protected function randomBan(string $actorType, string $actorId): array {
- return [
- 'id' => random_int(1, 1337),
- 'actorType' => $this->participant->getAttendee()->getActorType(),
- 'actorId' => $this->participant->getAttendee()->getActorId(),
- 'bannedType' => $actorType,
- 'bannedId' => $actorId,
- 'bannedTime' => random_int(1514747958, 1714747958),
- 'internalNote' => '#NOTE#' . $actorType . '#' . $actorId . '#' . sha1($actorType . $actorId),
- ];
- }
}
diff --git a/lib/Migration/Version20000Date20240621150333.php b/lib/Migration/Version20000Date20240621150333.php
new file mode 100644
index 000000000..ba7addbef
--- /dev/null
+++ b/lib/Migration/Version20000Date20240621150333.php
@@ -0,0 +1,70 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCA\Talk\Migration;
+
+use Closure;
+use OCP\DB\ISchemaWrapper;
+use OCP\DB\Types;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+class Version20000Date20240621150333 extends SimpleMigrationStep {
+ /**
+ * @param IOutput $output
+ * @param Closure(): ISchemaWrapper $schemaClosure
+ * @param array $options
+ * @return null|ISchemaWrapper
+ */
+ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
+ /** @var ISchemaWrapper $schema */
+ $schema = $schemaClosure();
+
+ if (!$schema->hasTable('talk_bans')) {
+ $table = $schema->createTable('talk_bans');
+ $table->addColumn('id', Types::BIGINT, [
+ 'autoincrement' => true,
+ 'notnull' => true,
+ 'length' => 20,
+ ]);
+ $table->addColumn('actor_type', Types::STRING, [
+ 'notnull' => true,
+ 'length' => 64,
+ ]);
+ $table->addColumn('actor_id', Types::STRING, [
+ 'notnull' => true,
+ 'length' => 64,
+ ]);
+ $table->addColumn('room_id', Types::BIGINT, [
+ 'notnull' => true,
+ 'unsigned' => true,
+ ]);
+ $table->addColumn('banned_type', Types::STRING, [
+ 'length' => 64,
+ 'notnull' => true,
+ ]);
+ $table->addColumn('banned_id', Types::STRING, [
+ 'length' => 64,
+ 'notnull' => true,
+ ]);
+ $table->addColumn('banned_time', Types::DATETIME, [
+ 'notnull' => false,
+ ]);
+ $table->addColumn('internal_note', Types::TEXT, [
+ 'notnull' => false,
+ ]);
+
+ $table->setPrimaryKey(['id']);
+ $table->addUniqueIndex(['banned_type', 'banned_id', 'room_id'], 'talk_bans_unique_actor_room'); //A user should not be banned from the same room more than once
+ $table->addIndex(['room_id']);
+ return $schema;
+ }
+
+ return null;
+ }
+}
diff --git a/lib/Model/Ban.php b/lib/Model/Ban.php
new file mode 100644
index 000000000..b2f1d686d
--- /dev/null
+++ b/lib/Model/Ban.php
@@ -0,0 +1,68 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCA\Talk\Model;
+
+use OCA\Talk\ResponseDefinitions;
+use OCP\AppFramework\Db\Entity;
+
+/**
+ * @psalm-import-type TalkBan from ResponseDefinitions
+ *
+ * @method void setId(int $id)
+ * @method int getId()
+ * @method void setActorType(string $actorType)
+ * @method string getActorType()
+ * @method void setActorId(string $actorId)
+ * @method string getActorId()
+ * @method void setRoomId(int $roomId)
+ * @method int getRoomId()
+ * @method void setBannedType(string $bannedType)
+ * @method string getBannedType()
+ * @method void setBannedId(string $bannedId)
+ * @method string getBannedId()
+ * @method void setBannedTime(\DateTime $bannedTime)
+ * @method \DateTime getBannedTime()
+ * @method void setInternalNote(null|string $internalNote)
+ * @method null|string getInternalNote()
+ */
+class Ban extends Entity implements \JsonSerializable {
+ protected string $actorType = '';
+ protected string $actorId = '';
+ protected int $roomId = 0;
+ protected string $bannedType = '';
+ protected string $bannedId = '';
+ protected ?\DateTime $bannedTime = null;
+ protected ?string $internalNote = null;
+
+ public function __construct() {
+ $this->addType('id', 'int');
+ $this->addType('actorType', 'string');
+ $this->addType('actorId', 'string');
+ $this->addType('roomId', 'int');
+ $this->addType('bannedType', 'string');
+ $this->addType('bannedId', 'string');
+ $this->addType('bannedTime', 'datetime');
+ $this->addType('internalNote', 'string');
+ }
+
+ /**
+ * @return TalkBan
+ */
+ public function jsonSerialize(): array {
+ return [
+ 'id' => $this->getId(),
+ 'actorType' => $this->getActorType(),
+ 'actorId' => $this->getActorId(),
+ 'bannedType' => $this->getBannedType(),
+ 'bannedId' => $this->getBannedId(),
+ 'bannedTime' => $this->getBannedTime()->getTimestamp(),
+ 'internalNote' => $this->getInternalNote() ?? '',
+ ];
+ }
+}
diff --git a/lib/Model/BanMapper.php b/lib/Model/BanMapper.php
new file mode 100644
index 000000000..01c979990
--- /dev/null
+++ b/lib/Model/BanMapper.php
@@ -0,0 +1,64 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCA\Talk\Model;
+
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Db\QBMapper;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+
+/**
+ * @method Ban mapRowToEntity(array $row)
+ * @method Ban findEntity(IQueryBuilder $query)
+ * @method Ban[] findEntities(IQueryBuilder $query)
+ * @template-extends QBMapper<Ban>
+ */
+class BanMapper extends QBMapper {
+
+ public function __construct(IDBConnection $db) {
+ parent::__construct($db, 'talk_bans', Ban::class);
+ }
+
+ /**
+ * @throws DoesNotExistException
+ */
+ public function findForActorAndRoom(string $actorId, string $actorType, int $roomId): Ban {
+ $query = $this->db->getQueryBuilder();
+ $query->select('*')
+ ->from($this->getTableName())
+ ->where($query->expr()->eq('actor_id', $query->createNamedParameter($actorId, IQueryBuilder::PARAM_STR)))
+ ->andWhere($query->expr()->eq('actor_type', $query->createNamedParameter($actorType, IQueryBuilder::PARAM_STR)))
+ ->andWhere($query->expr()->eq('room_id', $query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT)));
+
+ return $this->findEntity($query);
+ }
+
+ public function findByRoomId(int $roomId): array {
+ $query = $this->db->getQueryBuilder();
+ $query->select('*')
+ ->from($this->getTableName())
+ ->where($query->expr()->eq('room_id', $query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT)))
+ ->orderBy('id', 'ASC');
+
+ return $this->findEntities($query);
+ }
+
+ /**
+ * @throws DoesNotExistException
+ */
+ public function findByBanIdAndRoom(int $banId, int $roomId): Ban {
+ $query = $this->db->getQueryBuilder();
+ $query->select('*')
+ ->from($this->getTableName())
+ ->where($query->expr()->eq('id', $query->createNamedParameter($banId, IQueryBuilder::PARAM_INT)))
+ ->andWhere($query->expr()->eq('room_id', $query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT)));
+
+ return $this->findEntity($query);
+ }
+}
diff --git a/lib/Service/BanService.php b/lib/Service/BanService.php
new file mode 100644
index 000000000..f36f9013e
--- /dev/null
+++ b/lib/Service/BanService.php
@@ -0,0 +1,78 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCA\Talk\Service;
+
+use DateTime;
+use OCA\Talk\Model\Ban;
+use OCA\Talk\Model\BanMapper;
+use OCP\AppFramework\Db\DoesNotExistException;
+
+class BanService {
+
+ public function __construct(
+ protected BanMapper $banMapper,
+ ) {
+ }
+
+ /**
+ * Create a new ban
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function createBan(string $actorId, string $actorType, int $roomId, string $bannedId, string $bannedType, DateTime $bannedTime, string $internalNote): Ban {
+ if (empty($bannedId) || empty($bannedType)) {
+ throw new \InvalidArgumentException('bannedActor');
+ }
+
+ if (empty($internalNote)) {
+ throw new \InvalidArgumentException('internalNote');
+ }
+
+ $ban = new Ban();
+ $ban->setActorId($actorId);
+ $ban->setActorType($actorType);
+ $ban->setRoomId($roomId);
+ $ban->setBannedId($bannedId);
+ $ban->setBannedType($bannedType);
+ $ban->setBannedTime($bannedTime);
+ $ban->setInternalNote($internalNote);
+
+ return $this->banMapper->insert($ban);
+ }
+
+ /**
+ * Retrieve a ban for a specific actor and room.
+ *
+ * @throws DoesNotExistException
+ */
+ public function getBanForActorAndRoom(string $actorId, string $actorType, int $roomId): Ban {
+ return $this->banMapper->findForActorAndRoom($actorId, $actorType, $roomId);
+ }
+
+ /**
+ * Retrieve all bans for a specific room.
+ *
+ * @return Ban[]
+ */
+ public function getBansForRoom(int $roomId): array {
+ return $this->banMapper->findByRoomId($roomId);
+ }
+
+ /**
+ * Retrieve a ban by its ID and delete it.
+ */
+ public function findAndDeleteBanByIdForRoom(int $banId, int $roomId): void {
+ try {
+ $ban = $this->banMapper->findByBanIdAndRoom($banId, $roomId);
+ $this->banMapper->delete($ban);
+ } catch (DoesNotExistException) {
+ // Ban does not exist
+ }
+ }
+}
diff --git a/openapi-full.json b/openapi-full.json
index 2faa008ca..f9f21adc8 100644
--- a/openapi-full.json
+++ b/openapi-full.json
@@ -2099,7 +2099,11 @@
],
"properties": {
"error": {
- "type": "string"
+ "type": "string",
+ "en