Skip to content

Commit

Permalink
[Feature] Admin approval (#1232)
Browse files Browse the repository at this point in the history
Co-authored-by: TheVillageGuy <[email protected]>
  • Loading branch information
BentiGorlich and TheVillageGuy committed Dec 8, 2024
1 parent 3d87df3 commit eaa3390
Show file tree
Hide file tree
Showing 49 changed files with 926 additions and 19 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ S3_VERSION=
# Only let admins generated oauth clients
KBIN_ADMIN_ONLY_OAUTH_CLIENTS=false

# Manually approve every new user
MBIN_NEW_USERS_NEED_APPROVAL=false

# oAuth (optional)
OAUTH_AZURE_ID=
OAUTH_AZURE_SECRET=
Expand Down
3 changes: 3 additions & 0 deletions .env.example_docker
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ S3_VERSION=
# Only let admins generate oauth clients
KBIN_ADMIN_ONLY_OAUTH_CLIENTS=false

# Manually approve every new user
MBIN_NEW_USERS_NEED_APPROVAL=false

# oAuth (optional)
OAUTH_AZURE_ID=
OAUTH_AZURE_SECRET=
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
"twig/extra-bundle": "^3.10.0",
"twig/html-extra": "^3.10.0",
"twig/intl-extra": "^3.10.0",
"twig/twig": "^3.10.3",
"twig/twig": "^3.15.0",
"webmozart/assert": "^1.11.0",
"wohali/oauth2-discord-new": "^1.2.1"
},
Expand Down
6 changes: 3 additions & 3 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions config/mbin_routes/admin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,21 @@ admin_magazine_ownership_requests_reject:
path: /admin/magazine_ownership/{name}/{username}/reject
methods: [POST]

admin_signup_requests:
controller: App\Controller\Admin\AdminSignupRequestsController::requests
path: /admin/signup_requests
methods: [ GET ]

admin_signup_requests_approve:
controller: App\Controller\Admin\AdminSignupRequestsController::approve
path: /admin/signup_requests/{id}/approve
methods: [ POST ]

admin_signup_requests_reject:
controller: App\Controller\Admin\AdminSignupRequestsController::reject
path: /admin/signup_requests/{id}/reject
methods: [ POST ]

admin_cc:
controller: App\Controller\Admin\AdminClearCacheController
path: /admin/cc
Expand Down
18 changes: 18 additions & 0 deletions config/mbin_routes/admin_api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,21 @@ api_admin_purge_magazine:
path: /api/admin/magazine/{magazine_id}/purge
methods: [ DELETE ]
format: json

api_admin_view_user_applications:
controller: App\Controller\Api\User\Admin\UserApplicationApi::retrieve
path: /api/admin/users/applications
methods: [ GET ]
format: json

api_admin_view_user_application_approve:
controller: App\Controller\Api\User\Admin\UserApplicationApi::approve
path: /api/admin/users/applications/{user_id}/approve
methods: [ GET ]
format: json

api_admin_view_user_application_reject:
controller: App\Controller\Api\User\Admin\UserApplicationApi::reject
path: /api/admin/users/applications/{user_id}/reject
methods: [ GET ]
format: json
1 change: 1 addition & 0 deletions config/packages/doctrine.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ doctrine:
url: '%env(resolve:DATABASE_URL)%'
types:
citext: App\DoctrineExtensions\DBAL\Types\Citext
enumApplicationStatus: App\DoctrineExtensions\DBAL\Types\EnumApplicationStatus
mapping_types:
user_type: string
citext: citext
Expand Down
3 changes: 3 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ parameters:
mbin_downvotes_mode_default: 'enabled'
mbin_downvotes_mode: '%env(enum:\App\Utils\DownvotesMode:default:mbin_downvotes_mode_default:MBIN_DOWNVOTES_MODE)%'

mbin_new_users_need_approval: '%env(bool:default::MBIN_NEW_USERS_NEED_APPROVAL)%'

services:
# default configuration for services in *this* file
_defaults:
Expand Down Expand Up @@ -182,6 +184,7 @@ services:
$mbinSsoOnlyMode: '%sso_only_mode%'
$maxImageBytes: '%max_image_bytes%'
$mbinDownvotesMode: '%mbin_downvotes_mode%'
$mbinNewUsersNeedApproval: '%mbin_new_users_need_approval%'

# Markdown
App\Markdown\Factory\EnvironmentFactory:
Expand Down
10 changes: 10 additions & 0 deletions docs/02-admin/03-optional-features/user_application.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Manually Approving New Users

If you want to manually approve users before they can log into your server,
you can either tick the checkbox in the admin settings put this in the `.env` file:
```dotenv
MBIN_NEW_USERS_NEED_APPROVAL=true
```

You will then see a new admin panel called `Applications` where new users will appear until you approve or deny them.
When you have decided on one or the other, the user will get an email notification about it.
30 changes: 30 additions & 0 deletions migrations/Version20241104162329.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

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

final class Version20241104162655 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add application_text and application_status to the user table';
}

public function up(Schema $schema): void
{
$this->addSql('CREATE TYPE enumApplicationStatus AS ENUM (\'Approved\', \'Rejected\', \'Pending\')');
$this->addSql('ALTER TABLE "user" ADD application_text TEXT DEFAULT NULL');
$this->addSql('ALTER TABLE "user" ADD application_status enumApplicationStatus DEFAULT \'Approved\' NOT NULL');
}

public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE "user" DROP application_text');
$this->addSql('ALTER TABLE "user" DROP application_status');
$this->addSql('DROP TYPE enumApplicationStatus');
}
}
9 changes: 7 additions & 2 deletions src/Command/UserCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ protected function configure(): void
$this->addArgument('username', InputArgument::REQUIRED)
->addArgument('email', InputArgument::REQUIRED)
->addArgument('password', InputArgument::REQUIRED)
->addOption('applicationText', 'a', InputOption::VALUE_REQUIRED, 'The application text of the user, if set the user will not be pre-approved')
->addOption('remove', 'r', InputOption::VALUE_NONE, 'Remove user')
->addOption('admin', null, InputOption::VALUE_NONE, 'Grant administrator privileges')
->addOption('moderator', null, InputOption::VALUE_NONE, 'Grant global moderator privileges');
Expand Down Expand Up @@ -69,10 +70,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int

private function createUser(InputInterface $input, SymfonyStyle $io): void
{
$dto = (new UserDto())->create($input->getArgument('username'), $input->getArgument('email'));
$applicationText = $input->getOption('applicationText');
if ('' === $applicationText) {
$applicationText = null;
}
$dto = (new UserDto())->create($input->getArgument('username'), $input->getArgument('email'), applicationText: $applicationText);
$dto->plainPassword = $input->getArgument('password');

$user = $this->manager->create($dto, false, false);
$user = $this->manager->create($dto, false, false, preApprove: null === $applicationText);

if ($input->getOption('admin')) {
$user->setOrRemoveAdminRole();
Expand Down
5 changes: 5 additions & 0 deletions src/Controller/ActivityPub/User/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use App\Controller\AbstractController;
use App\Entity\User;
use App\Enums\EApplicationStatus;
use App\Factory\ActivityPub\PersonFactory;
use App\Factory\ActivityPub\TombstoneFactory;
use Symfony\Component\HttpFoundation\JsonResponse;
Expand All @@ -25,6 +26,10 @@ public function __invoke(User $user, Request $request): JsonResponse
throw $this->createNotFoundException();
}

if (EApplicationStatus::Approved !== $user->getApplicationStatus()) {
throw $this->createNotFoundException();
}

if (!$user->isDeleted || null !== $user->markedForDeletionAt) {
$response = new JsonResponse($this->personFactory->create($user, true));
} else {
Expand Down
50 changes: 50 additions & 0 deletions src/Controller/Admin/AdminSignupRequestsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace App\Controller\Admin;

use App\Controller\AbstractController;
use App\Entity\User;
use App\Repository\UserRepository;
use App\Service\UserManager;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\MapQueryParameter;
use Symfony\Component\Security\Http\Attribute\IsGranted;

class AdminSignupRequestsController extends AbstractController
{
public function __construct(
private readonly UserRepository $repository,
private readonly UserManager $userManager,
) {
}

#[IsGranted('ROLE_ADMIN')]
public function requests(#[MapQueryParameter] int $page): Response
{
$requests = $this->repository->findAllSignupRequestsPaginated($page);

return $this->render('admin/signup_requests.html.twig', [
'requests' => $requests,
'page' => $page,
]);
}

#[IsGranted('ROLE_ADMIN')]
public function approve(#[MapQueryParameter] int $page, #[MapEntity(id: 'id')] User $user): Response
{
$this->userManager->approveUserApplication($user);

return $this->redirectToRoute('admin_signup_requests', ['page' => $page]);
}

#[IsGranted('ROLE_ADMIN')]
public function reject(#[MapQueryParameter] int $page, #[MapEntity(id: 'id')] User $user): Response
{
$this->userManager->rejectUserApplication($user);

return $this->redirectToRoute('admin_signup_requests', ['page' => $page]);
}
}
4 changes: 4 additions & 0 deletions src/Controller/Api/BaseApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,12 @@
use App\Repository\PostCommentRepository;
use App\Repository\PostRepository;
use App\Repository\TagLinkRepository;
use App\Repository\UserRepository;
use App\Schema\PaginationSchema;
use App\Service\BookmarkManager;
use App\Service\IpResolver;
use App\Service\ReportManager;
use App\Service\UserManager;
use Doctrine\ORM\EntityManagerInterface;
use League\Bundle\OAuth2ServerBundle\Model\AccessToken;
use League\Bundle\OAuth2ServerBundle\Security\Authentication\Token\OAuth2Token;
Expand Down Expand Up @@ -91,6 +93,8 @@ public function __construct(
protected readonly BookmarkListRepository $bookmarkListRepository,
protected readonly BookmarkRepository $bookmarkRepository,
protected readonly BookmarkManager $bookmarkManager,
protected readonly UserManager $userManager,
protected readonly UserRepository $userRepository,
private readonly ImageRepository $imageRepository,
private readonly ReportManager $reportManager,
private readonly OAuth2ClientAccessRepository $clientAccessRepository,
Expand Down
Loading

0 comments on commit eaa3390

Please sign in to comment.