Added an console command to convert local to SAML users and vice versa

This commit is contained in:
Jan Böhmer 2023-02-23 23:36:40 +01:00
parent c5904303e3
commit c831d57614
5 changed files with 165 additions and 4 deletions

View file

@ -197,6 +197,10 @@ services:
arguments:
$available_themes: '%partdb.available_themes%'
App\Command\User\ConvertToSAMLUserCommand:
arguments:
$saml_enabled: '%partdb.saml.enabled%'
####################################################################################################################
# Label system

View file

@ -0,0 +1,115 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Command\User;
use App\Entity\UserSystem\User;
use App\Security\SamlUserFactory;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class ConvertToSAMLUserCommand extends Command
{
protected static $defaultName = 'partdb:user:convert-to-saml-user|partdb:users:convert-to-saml-user';
protected EntityManagerInterface $entityManager;
protected bool $saml_enabled;
public function __construct(EntityManagerInterface $entityManager, bool $saml_enabled)
{
parent::__construct();
$this->entityManager = $entityManager;
$this->saml_enabled = $saml_enabled;
}
protected function configure(): void
{
$this
->setDescription('Converts a local user to a SAML user (and vice versa)')
->setHelp('This converts a local user, which can login via the login form, to a SAML user, which can only login via SAML. This is useful if you want to migrate from a local user system to a SAML user system.')
->addArgument('user', InputArgument::REQUIRED, 'The username (or email) of the user')
->addOption('to-local', null, InputOption::VALUE_NONE, 'Converts a SAML user to a local user')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$user_name = $input->getArgument('user');
$to_local = $input->getOption('to-local');
if (!$this->saml_enabled && !$to_local) {
$io->confirm('SAML login is not configured. You will not be able to login with this user anymore, when SSO is not configured. Do you really want to continue?');
}
/** @var User $user */
$user = $this->entityManager->getRepository(User::class)->findByEmailOrName($user_name);
if (!$user) {
$io->error('User not found!');
return 1;
}
$io->info('User found: '.$user->getFullName(true) . ': '.$user->getEmail().' [ID: ' . $user->getID() . ']');
if ($to_local) {
return $this->toLocal($user, $io);
}
return $this->toSAML($user, $io);
}
public function toLocal(User $user, SymfonyStyle $io): int
{
$io->confirm('You are going to convert a SAML user to a local user. This means, that the user can only login via the login form. '
. 'The permissions and groups settings of the user will remain unchanged. Do you really want to continue?');
$user->setSAMLUser(false);
$user->setPassword(SamlUserFactory::SAML_PASSWORD_PLACEHOLDER);
$this->entityManager->flush();
$io->success('User converted to local user! You will need to set a password for this user, before you can login with it.');
return 0;
}
public function toSAML(User $user, SymfonyStyle $io): int
{
$io->confirm('You are going to convert a local user to a SAML user. This means, that the user can only login via SAML afterwards. The password in the DB will be removed. '
. 'The permissions and groups settings of the user will remain unchanged. Do you really want to continue?');
$user->setSAMLUser(true);
$user->setPassword(SamlUserFactory::SAML_PASSWORD_PLACEHOLDER);
$this->entityManager->flush();
$io->success('User converted to SAML user! You can now login with this user via SAML.');
return 0;
}
}

View file

@ -46,22 +46,39 @@ class UserListCommand extends Command
$this
->setDescription('Lists all users')
->setHelp('This command lists all users in the database.')
->addOption('local', 'l', null, 'Only list local users')
->addOption('saml', 's', null, 'Only list SAML users')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$only_local = $input->getOption('local');
$only_saml = $input->getOption('saml');
//Get all users from database
$users = $this->entityManager->getRepository(User::class)->findAll();
if ($only_local && $only_saml) {
$io->error('You can not use --local and --saml at the same time!');
return Command::FAILURE;
}
$repo = $this->entityManager->getRepository(User::class);
if ($only_local) {
$users = $repo->onlyLocalUsers();
} elseif ($only_saml) {
$users = $repo->onlySAMLUsers();
} else {
$users = $repo->findAll();
}
$io->info(sprintf("Found %d users in database.", count($users)));
$io->title('Users:');
$table = new Table($output);
$table->setHeaders(['ID', 'Username', 'Name', 'Email', 'Group', 'Login Disabled']);
$table->setHeaders(['ID', 'Username', 'Name', 'Email', 'Group', 'Login Disabled', 'Type']);
foreach ($users as $user) {
$table->addRow([
@ -71,6 +88,7 @@ class UserListCommand extends Command
$user->getEmail(),
$user->getGroup() !== null ? $user->getGroup()->getName() . ' (ID: ' . $user->getGroup()->getID() . ')' : 'No group',
$user->isDisabled() ? 'Yes' : 'No',
$user->isSAMLUser() ? 'SAML' : 'Local',
]);
}

View file

@ -89,4 +89,26 @@ final class UserRepository extends NamedDBElementRepository implements PasswordU
$this->getEntityManager()->flush();
}
}
/**
* Returns the list of all local users (not SAML users).
* @return User[]
*/
public function onlyLocalUsers(): array
{
return $this->findBy([
'saml_user' => false,
]);
}
/**
* Returns the list of all SAML users.
* @return User[]
*/
public function onlySAMLUsers(): array
{
return $this->findBy([
'saml_user' => true,
]);
}
}

View file

@ -26,12 +26,14 @@ use Symfony\Component\Security\Core\User\UserInterface;
class SamlUserFactory implements SamlUserFactoryInterface
{
public const SAML_PASSWORD_PLACEHOLDER = '!!SAML!!';
public function createUser($username, array $attributes = []): UserInterface
{
$user = new User();
$user->setName($username);
$user->setNeedPwChange(false);
$user->setPassword('!!SAML!!');
$user->setPassword(self::SAML_PASSWORD_PLACEHOLDER);
//This is a SAML user now!
$user->setSamlUser(true);