Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
5 changes: 5 additions & 0 deletions config/mbin_routes/admin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ admin_federation_deny_instance:
path: /admin/federation/deny
methods: [GET, POST]

admin_federation_block_instance:
controller: App\Controller\Admin\AdminFederationController::blockInstanceGlobally
path: /admin/federation/block
methods: [GET]

admin_pages:
controller: App\Controller\Admin\AdminPagesController
path: /admin/pages/{page}
Expand Down
6 changes: 6 additions & 0 deletions config/mbin_routes/admin_api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ api_admin_deny_instance:
methods: [ PUT ]
format: json

api_admin_block_instance:
controller: App\Controller\Api\Instance\Admin\InstanceBlockGloballyApi::blockForAll
path: /api/admin/instance/block
methods: [ POST ]
format: json

api_admin_update_pages:
controller: App\Controller\Api\Instance\Admin\InstanceUpdatePagesApi
path: /api/instance/{page}
Expand Down
14 changes: 14 additions & 0 deletions config/mbin_routes/federation.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
page_federation:
controller: App\Controller\FederationController
path: /federation
methods: [ GET ]

federation_user_block_instance:
controller: App\Controller\FederationController::userBlockInstance
path: /federation/user/block
methods: [GET]

federation_user_unblock_instance:
controller: App\Controller\FederationController::userUnblockInstance
path: /federation/user/unblock
methods: [GET]
5 changes: 0 additions & 5 deletions config/mbin_routes/page.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,6 @@ stats:
path: /stats/{statsType}/{statsPeriod}/{withFederated}
methods: [ GET ]

page_federation:
controller: App\Controller\FederationController
path: /federation
methods: [ GET ]

redirect_instances:
controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController
path: /instances
Expand Down
5 changes: 5 additions & 0 deletions config/mbin_routes/user.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ user_settings_user_blocks:
path: /settings/blocked/people
methods: [GET]

user_settings_instance_blocks:
controller: App\Controller\User\Profile\UserBlockController::instances
path: /settings/blocked/instances
methods: [GET]

user_settings_magazine_subscriptions:
controller: App\Controller\User\Profile\UserSubController::magazines
path: /settings/subscriptions/magazines
Expand Down
18 changes: 18 additions & 0 deletions config/mbin_routes/user_api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,24 @@ api_user_retrieve_filter_lists_delete:
methods: [ DELETE ]
format: json

api_user_instance_blocks_retrieve:
controller: App\Controller\Api\Instance\InstanceUserBlockApi::retrieve
path: /api/users/instanceBlocks
methods: [ GET ]
format: json

api_user_instance_blocks_block:
controller: App\Controller\Api\Instance\InstanceUserBlockApi::block
path: /api/users/instanceBlocks/block
methods: [ POST ]
format: json

api_user_instance_blocks_unblock:
controller: App\Controller\Api\Instance\InstanceUserBlockApi::unblock
path: /api/users/instanceBlocks/unblock
methods: [ POST ]
format: json

api_user_update_avatar:
controller: App\Controller\Api\User\UserUpdateImagesApi::avatar
path: /api/users/avatar
Expand Down
33 changes: 33 additions & 0 deletions migrations/Version20260603205601.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

final class Version20260603205601 extends AbstractMigration
{
public function getDescription(): string
{
return 'add InstanceBlock';
}

public function up(Schema $schema): void
{
$this->addSql('CREATE SEQUENCE instance_block_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE instance_block (id INT NOT NULL, user_id INT NOT NULL, instance_id INT NOT NULL, instance_domain TEXT NOT NULL, PRIMARY KEY (id))');
$this->addSql('CREATE UNIQUE INDEX instance_block_idx ON instance_block (user_id, instance_domain)');
$this->addSql('ALTER TABLE instance_block ADD CONSTRAINT FK_8F85864AA76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE NOT DEFERRABLE');
$this->addSql('ALTER TABLE instance_block ADD CONSTRAINT FK_8F85864A3A51721D FOREIGN KEY (instance_id) REFERENCES instance (id) ON DELETE CASCADE NOT DEFERRABLE');
}

public function down(Schema $schema): void
{
$this->addSql('DROP SEQUENCE instance_block_id_seq CASCADE');
$this->addSql('ALTER TABLE instance_block DROP CONSTRAINT FK_8F85864AA76ED395');
$this->addSql('ALTER TABLE instance_block DROP CONSTRAINT FK_8F85864A3A51721D');
$this->addSql('DROP TABLE instance_block');
}
}
15 changes: 15 additions & 0 deletions src/Controller/Admin/AdminFederationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\MapQueryParameter;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Http\Attribute\IsGranted;

class AdminFederationController extends AbstractController
Expand Down Expand Up @@ -136,4 +137,18 @@ public function denyInstance(#[MapQueryParameter] string $instanceDomain, Reques
'useAllowList' => $this->settingsManager->getUseAllowList(),
], new Response(status: $form->isSubmitted() && !$form->isValid() ? 422 : 200));
}

#[IsGranted('ROLE_ADMIN')]
public function blockInstanceGlobally(#[MapQueryParameter] string $instanceDomain): Response
{
$instance = $this->instanceRepository->findOneBy(['domain' => $instanceDomain]);

if (null === $instance) {
throw new NotFoundHttpException('instance '.$instanceDomain.' not found');
}

$this->instanceManager->blockInstancesGlobally([$instance]);

return $this->redirectToRoute('admin_federation');
}
}
2 changes: 2 additions & 0 deletions src/Controller/Api/BaseApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
use App\Repository\EntryCommentRepository;
use App\Repository\EntryRepository;
use App\Repository\ImageRepository;
use App\Repository\InstanceBlockRepository;
use App\Repository\InstanceRepository;
use App\Repository\NotificationSettingsRepository;
use App\Repository\OAuth2ClientAccessRepository;
Expand Down Expand Up @@ -127,6 +128,7 @@ public function __construct(
protected readonly UserFactory $userFactory,
protected readonly ReputationRepository $reputationRepository,
protected readonly InstanceRepository $instanceRepository,
protected readonly InstanceBlockRepository $instanceBlockRepository,
protected readonly InstanceManager $instanceManager,
protected readonly TranslatorInterface $translator,
) {
Expand Down
74 changes: 74 additions & 0 deletions src/Controller/Api/Instance/Admin/InstanceBlockGloballyApi.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace App\Controller\Api\Instance\Admin;

use App\Controller\Api\Instance\InstanceBaseApi;
use App\DTO\InstanceDomainsRequestDto;
use App\Schema\Errors\BadRequestErrorSchema;
use App\Schema\Errors\NotFoundErrorSchema;
use App\Schema\Errors\TooManyRequestsErrorSchema;
use App\Schema\Errors\UnauthorizedErrorSchema;
use Nelmio\ApiDocBundle\Attribute\Model;
use Nelmio\ApiDocBundle\Attribute\Security;
use OpenApi\Attributes as OA;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\RateLimiter\RateLimiterFactoryInterface;
use Symfony\Component\Security\Http\Attribute\IsGranted;

class InstanceBlockGloballyApi extends InstanceBaseApi

Check failure

Code scanning / Psalm

PropertyNotSetInConstructor Error

Property App\Controller\Api\Instance\Admin\InstanceBlockGloballyApi::$container is not defined in constructor of App\Controller\Api\Instance\Admin\InstanceBlockGloballyApi or in any methods called in the constructor

Check failure

Code scanning / Psalm

MethodSignatureMismatch Error

Method App\Controller\Api\Instance\Admin\InstanceBlockGloballyApi::validateCsrf has fewer parameters than parent method App\Controller\AbstractController::validateCsrf

Check failure

Code scanning / Psalm

UnusedClass Error

Class App\Controller\Api\Instance\Admin\InstanceBlockGloballyApi is never used

Check failure

Code scanning / Psalm

ClassMustBeFinal Error

Class App\Controller\Api\Instance\Admin\InstanceBlockGloballyApi is never extended and is not part of the public API, and thus must be made final.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
{
#[OA\RequestBody(content: new Model(
type: InstanceDomainsRequestDto::class,
))]
#[OA\Response(
response: 204,
description: 'Instance is blocked',
headers: [
new OA\Header(header: 'X-RateLimit-Remaining', description: 'Number of requests left until you will be rate limited', schema: new OA\Schema(type: 'integer')),
new OA\Header(header: 'X-RateLimit-Retry-After', description: 'Unix timestamp to retry the request after', schema: new OA\Schema(type: 'integer')),
new OA\Header(header: 'X-RateLimit-Limit', description: 'Number of requests available', schema: new OA\Schema(type: 'integer')),
],
)]
#[OA\Response(
response: 400,
description: 'Instance domain not set in request-body',
content: new OA\JsonContent(ref: new Model(type: BadRequestErrorSchema::class))
)]
#[OA\Response(
response: 401,
description: 'Permission denied due to missing or expired token',
content: new OA\JsonContent(ref: new Model(type: UnauthorizedErrorSchema::class))
)]
#[OA\Response(
response: 404,
description: 'Instance with given domain was not found',
content: new OA\JsonContent(ref: new Model(type: NotFoundErrorSchema::class))
)]
#[OA\Response(
response: 429,
description: 'You are being rate limited',
headers: [
new OA\Header(header: 'X-RateLimit-Remaining', description: 'Number of requests left until you will be rate limited', schema: new OA\Schema(type: 'integer')),
new OA\Header(header: 'X-RateLimit-Retry-After', description: 'Unix timestamp to retry the request after', schema: new OA\Schema(type: 'integer')),
new OA\Header(header: 'X-RateLimit-Limit', description: 'Number of requests available', schema: new OA\Schema(type: 'integer')),
],
content: new OA\JsonContent(ref: new Model(type: TooManyRequestsErrorSchema::class))
)]
#[OA\Tag(name: 'admin/instance')]
#[IsGranted('ROLE_ADMIN')]
#[Security(name: 'oauth2', scopes: ['admin:federation:update'])]
#[IsGranted('ROLE_OAUTH2_ADMIN:FEDERATION:UPDATE')]
public function blockForAll(
RateLimiterFactoryInterface $apiModerateLimiter,
): JsonResponse {
$headers = $this->rateLimit($apiModerateLimiter);

$instances = $this->getInstancesFromDomainsRequest();

$this->instanceManager->blockInstancesGlobally($instances);

return new JsonResponse(status: 204, headers: $headers);
}
}
25 changes: 25 additions & 0 deletions src/Controller/Api/Instance/InstanceBaseApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,32 @@
namespace App\Controller\Api\Instance;

use App\Controller\Api\BaseApi;
use App\DTO\InstanceDomainsRequestDto;
use App\Entity\Instance;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class InstanceBaseApi extends BaseApi
{
/**
* @return Instance[]
*/
protected function getInstancesFromDomainsRequest(): array
{
/** @var InstanceDomainsRequestDto $domains */

Check failure

Code scanning / Psalm

UnnecessaryVarAnnotation Error

The @var App\DTO\InstanceDomainsRequestDto annotation for $domains is unnecessary
$domains = $this->serializer->deserialize($this->request->getCurrentRequest()->getContent(), InstanceDomainsRequestDto::class, 'json');

Check notice

Code scanning / Psalm

PossiblyNullReference Note

Cannot call method getContent on possibly null value
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed

if (empty($domains->domains)) {
throw new BadRequestException('domains must not be empty');
}

return array_map(function ($domain) {
$instance = $this->instanceRepository->findOneBy(['domain' => $domain]);
if (null === $instance) {
throw new NotFoundHttpException('instance '.$domain.' not found');

Check failure

Code scanning / Psalm

MixedOperand Error

Right operand cannot be mixed
}

return $instance;
}, $domains->domains);
}
}
Loading
Loading