Skip to content

Commit

Permalink
feat(ban): Contract for banning attendees and IP/ranges
Browse files Browse the repository at this point in the history
Signed-off-by: Joas Schilling <[email protected]>
  • Loading branch information
nickvergessen committed May 3, 2024
1 parent f4cae12 commit ae9e0af
Show file tree
Hide file tree
Showing 12 changed files with 1,250 additions and 6 deletions.
1 change: 1 addition & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

return array_merge_recursive(
include(__DIR__ . '/routes/routesAvatarController.php'),
include(__DIR__ . '/routes/routesBanController.php'),
include(__DIR__ . '/routes/routesBotController.php'),
include(__DIR__ . '/routes/routesBreakoutRoomController.php'),
include(__DIR__ . '/routes/routesCallController.php'),
Expand Down
22 changes: 22 additions & 0 deletions appinfo/routes/routesBanController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

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

$requirements = [
'apiVersion' => '(v1)',
'token' => '[a-z0-9]{4,30}',
];
return [
'ocs' => [
/** @see \OCA\Talk\Controller\BanController::banActor() */
['name' => 'Ban#banActor', 'url' => '/api/{apiVersion}/ban/{token}', 'verb' => 'POST', 'requirements' => $requirements],
/** @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],
],
];
3 changes: 3 additions & 0 deletions docs/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,6 @@
* `config => federation => incoming-enabled` - Boolean, whether users are allowed to be invited into federated conversations on other servers
* `config => federation => outgoing-enabled` - Boolean, whether users are allowed to invited federated users of other servers into conversations
* `config => federation => only-trusted-servers` - Boolean, whether federation invites are limited to trusted servers

## 20
* `ban-v1` - Whether the API to ban attendees is available
1 change: 1 addition & 0 deletions lib/Capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ public function getCapabilities(): array {
'silent-send-state',
'chat-read-last',
'federation-v1',
'ban-v1',
],
'config' => [
'attachments' => [
Expand Down
126 changes: 126 additions & 0 deletions lib/Controller/BanController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Talk\Controller;

use OCA\Talk\Middleware\Attribute\RequireModeratorParticipant;
use OCA\Talk\Model\Attendee;
use OCA\Talk\ResponseDefinitions;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;

/**
* @psalm-import-type TalkBan from ResponseDefinitions
*/
class BanController extends AEnvironmentAwareController {
public function __construct(
string $appName,
IRequest $request,
) {
parent::__construct($appName, $request);
}

/**
* Ban an actor or IP address
*
* Required capability: `ban-v1`
*
* @param string $token Conversation token
* @param 'users'|'groups'|'circles'|'emails'|'federated_users'|'phones'|'ip' $actorType Type of actor to ban, or `ip` when banning a clients remote address
* @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{}>
*
* 200: Ban successfully
* 400: Actor information is invalid
*/
#[PublicPage]
#[RequireModeratorParticipant]
public function banActor(string $token, string $actorType, string $actorId, string $internalNote): DataResponse {
if ($actorId === 'wrong') {
return new DataResponse([
'error' => 'actor',
], 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
);
}

/**
* List the bans of a conversation
*
* Required capability: `ban-v1`
*
* @param string $token Conversation token
* @return DataResponse<Http::STATUS_OK, list<TalkBan>, array{}>
*
* 200: List all bans
*/
#[PublicPage]
#[RequireModeratorParticipant]
public function listBans(string $token): DataResponse {
return new DataResponse([
$this->randomBan(Attendee::ACTOR_USERS, 'test'),
$this->randomBan(Attendee::ACTOR_USERS, '123456'),
$this->randomBan(Attendee::ACTOR_FEDERATED_USERS, '[email protected]'),
$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);
}

/**
* Unban an actor or IP address
*
* Required capability: `ban-v1`
*
* @param string $token Conversation token
* @param int $banId ID of the ban to be removed
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
*
* 200: Unban successfully or not found
*/
#[PublicPage]
#[RequireModeratorParticipant]
public function unbanActor(string $token, int $banId): DataResponse {
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),
];
}
}
18 changes: 16 additions & 2 deletions lib/Controller/RoomController.php
Original file line number Diff line number Diff line change
Expand Up @@ -1474,7 +1474,7 @@ public function setPassword(string $password): DataResponse {
* @param string $token Token of the room
* @param string $password Password of the room
* @param bool $force Create a new session if necessary
* @return DataResponse<Http::STATUS_OK, TalkRoom, array{X-Nextcloud-Talk-Proxy-Hash?: string}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>|DataResponse<Http::STATUS_CONFLICT, array{sessionId: string, inCall: int, lastPing: int}, array{}>
* @return DataResponse<Http::STATUS_OK, TalkRoom, array{X-Nextcloud-Talk-Proxy-Hash?: string}>|DataResponse<Http::STATUS_FORBIDDEN, array{error: 'ban'|'password'}, array<empty>>|DataResponse<Http::STATUS_NOT_FOUND, array<empty>, array<empty>>|DataResponse<Http::STATUS_CONFLICT, array{sessionId: string, inCall: int, lastPing: int}, array<empty>>
*
* 200: Room joined successfully
* 403: Joining room is not allowed
Expand Down Expand Up @@ -1512,6 +1512,18 @@ public function joinRoom(string $token, string $password = '', bool $force = tru
return $response;
}

if (strtolower($room->getName()) === 'ban user' && $this->userId === 'banned') {
return new DataResponse([
'error' => 'ban',
], Http::STATUS_FORBIDDEN);
}

if (strtolower($room->getName()) === 'ban guest' && !$this->userId) {
return new DataResponse([
'error' => 'ban',
], Http::STATUS_FORBIDDEN);
}

/** @var Participant|null $previousSession */
$previousParticipant = null;
/** @var Session|null $previousSession */
Expand Down Expand Up @@ -1585,7 +1597,9 @@ public function joinRoom(string $token, string $password = '', bool $force = tru
$this->throttler->resetDelay($this->request->getRemoteAddress(), 'talkRoomPassword', ['token' => $token, 'action' => 'talkRoomPassword']);
$this->throttler->resetDelay($this->request->getRemoteAddress(), 'talkRoomToken', ['token' => $token, 'action' => 'talkRoomToken']);
} catch (InvalidPasswordException $e) {
$response = new DataResponse([], Http::STATUS_FORBIDDEN);
$response = new DataResponse([
'error' => 'password',
], Http::STATUS_FORBIDDEN);
$response->throttle(['token' => $token, 'action' => 'talkRoomPassword']);
return $response;
} catch (UnauthorizedException $e) {
Expand Down
10 changes: 10 additions & 0 deletions lib/ResponseDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@
namespace OCA\Talk;

/**
* @psalm-type TalkBan = array{
* id: int,
* actorType: string,
* actorId: string,
* bannedType: string,
* bannedId: string,
* bannedTime: int,
* internalNote: string,
* }
*
* @psalm-type TalkBot = array{
* description: ?string,
* id: int,
Expand Down
Loading

0 comments on commit ae9e0af

Please sign in to comment.