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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

β›” feat(ban): Contract for banning attendees and IP/ranges #12259

Merged
merged 1 commit into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
123 changes: 123 additions & 0 deletions lib/Controller/BanController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?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 '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
nickvergessen marked this conversation as resolved.
Show resolved Hide resolved
* @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 $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(),
nickvergessen marked this conversation as resolved.
Show resolved Hide resolved
'internalNote' => $internalNote ?: 'Lorem ipsum',
],
Http::STATUS_OK
);
}

/**
* List the bans of a conversation
*
* Required capability: `ban-v1`
*
* @return DataResponse<Http::STATUS_OK, list<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);
}

/**
* Unban an actor or IP address
*
* Required capability: `ban-v1`
*
* @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(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
Loading