diff --git a/CHANGELOG.md b/CHANGELOG.md index 16a29985..b9d49ad3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,14 +3,15 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - +- Update docker build to publish to "os2display" org on docker hub. Update github workflow to latest actions. +- Set up separate image builds for itkdev and os2display - Updated `EventDatabaseApiFeedType` query ensuring started but not finished events are found. - Refactored all feed related classes and services - Minor update of composer packages - Updated psalm to version 5.x - Fixed feed data provider id issue [#151](https://github.com/os2display/display-api-service/pull/151) -- Set up separate image builds for itkdev and os2display +- Updated add user command to ask which tenants user belongs to ## [1.2.8] - 2023-05-25 diff --git a/src/Command/Users/AddTenantCommand.php b/src/Command/Tenant/AddTenantCommand.php similarity index 99% rename from src/Command/Users/AddTenantCommand.php rename to src/Command/Tenant/AddTenantCommand.php index 17fe4234..c74652f1 100644 --- a/src/Command/Users/AddTenantCommand.php +++ b/src/Command/Tenant/AddTenantCommand.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace App\Command\Users; +namespace App\Command\Tenant; use App\Entity\Tenant; use App\Repository\TenantRepository; diff --git a/src/Command/Users/ConfigureTenantCommand.php b/src/Command/Tenant/ConfigureTenantCommand.php similarity index 98% rename from src/Command/Users/ConfigureTenantCommand.php rename to src/Command/Tenant/ConfigureTenantCommand.php index 40019a4a..a14b2b2e 100644 --- a/src/Command/Users/ConfigureTenantCommand.php +++ b/src/Command/Tenant/ConfigureTenantCommand.php @@ -1,6 +1,6 @@ addArgument('email', InputArgument::OPTIONAL, 'The email of the new user') ->addArgument('password', InputArgument::OPTIONAL, 'The plain password of the new user') ->addArgument('full-name', InputArgument::OPTIONAL, 'The full name of the new user') - ->addOption('admin', null, InputOption::VALUE_NONE, 'If set, the user is created as an administrator') + ->addArgument('role', InputArgument::OPTIONAL, 'The role of the user [editor|admin]') + ->addArgument('tenant-keys', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'The keys of the tenants the user should belong to (separate multiple keys with a space)') ; } @@ -109,7 +111,12 @@ protected function initialize(InputInterface $input, OutputInterface $output): v */ protected function interact(InputInterface $input, OutputInterface $output): void { - if (null !== $input->getArgument('email') && null !== $input->getArgument('password') && null !== $input->getArgument('full-name')) { + if (null !== $input->getArgument('email') && + null !== $input->getArgument('password') && + null !== $input->getArgument('full-name') && + null !== $input->getArgument('role') && + null !== $input->getArgument('tenant-keys') + ) { return; } @@ -149,6 +156,39 @@ protected function interact(InputInterface $input, OutputInterface $output): voi $fullName = $this->io->ask('Full Name', null, [$this->validator, 'validateFullName']); $input->setArgument('full-name', $fullName); } + + $helper = $this->getHelper('question'); + + // Ask for the role if it's not defined + $role = $input->getArgument('role'); + if (null !== $role) { + $this->io->text(' > Role: '.$role); + } else { + $question = new ChoiceQuestion( + 'Please select the user\'s role (defaults to editor)', + CommandInputValidator::ALLOWED_USER_ROLES, + 0 + ); + $question->setErrorMessage('Role %s is invalid.'); + + $role = $helper->ask($input, $output, $question); + $output->writeln('You have just selected: '.$role); + } + + // Ask for the tenant keys if it's not defined + $tenantKeys = $input->getArgument('tenant-keys'); + if (0 < \count($tenantKeys)) { + $this->io->text(' > Tenant Keys: '.$tenantKeys); + } else { + $question = new ChoiceQuestion( + 'Please select the tenant(s) the user should belong to (to select multiple answer with a list. E.g: "key1, key3")', + $this->getTenantsChoiceList(), + ); + $question->setMultiselect(true); + + $tenantKeys = $helper->ask($input, $output, $question); + $output->writeln('You have just selected: '.implode(', ', $tenantKeys)); + } } /** @@ -163,10 +203,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int $email = $input->getArgument('email'); $plainPassword = $input->getArgument('password'); $fullName = $input->getArgument('full-name'); - $isAdmin = $input->getOption('admin'); + $role = $input->getArgument('role'); + $tenantKeys = $input->getArgument('tenant-keys'); // make sure to validate the user data is correct - $this->validateUserData($email, $plainPassword, $fullName); + $this->validateUserData($email, $plainPassword, $fullName, $role, $tenantKeys); // create the user and hash its password $user = new User(); @@ -179,19 +220,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int $hashedPassword = $this->passwordHasher->hashPassword($user, $plainPassword); $user->setPassword($hashedPassword); - // @TODO Make it possible to only select specific Tenants - $tenants = $this->tenants->findAll(); - foreach ($tenants as $tenant) { + foreach ($tenantKeys as $tenantKey) { + $tenant = $this->tenantRepository->findOneBy(['tenantKey' => $tenantKey]); $userRoleTenant = new UserRoleTenant(); $userRoleTenant->setTenant($tenant); - $userRoleTenant->setRoles([$isAdmin ? 'ROLE_ADMIN' : 'ROLE_EDITOR']); + $userRoleTenant->setRoles(['ROLE_'.strtoupper($role)]); $user->addUserRoleTenant($userRoleTenant); } $this->entityManager->persist($user); $this->entityManager->flush(); - $this->io->success(sprintf('%s was successfully created: %s', $isAdmin ? 'Administrator user' : 'User', $user->getUserIdentifier())); + $this->io->success(sprintf('%s was successfully created: %s', ucfirst($role), $user->getUserIdentifier())); $event = $stopwatch->stop('add-user-command'); if ($output->isVerbose()) { @@ -201,7 +241,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Command::SUCCESS; } - private function validateUserData($email, $plainPassword, $fullName): void + private function validateUserData($email, $plainPassword, $fullName, $role, $tenantKeys): void { // first check if a user with the same username already exists. $existingUser = $this->users->findOneBy(['email' => $email]); @@ -214,6 +254,19 @@ private function validateUserData($email, $plainPassword, $fullName): void $this->validator->validatePassword($plainPassword); $this->validator->validateEmail($email); $this->validator->validateFullName($fullName); + $this->validator->validateRole($role); + $this->validator->validateTenantKeys($tenantKeys); + } + + private function getTenantsChoiceList(): array + { + $tenants = []; + /** @var Tenant $tenant */ + foreach ($this->tenantRepository->findBy([], ['tenantKey' => 'ASC']) as $tenant) { + $tenants[$tenant->getTenantKey()] = $tenant->getDescription(); + } + + return $tenants; } /** @@ -226,17 +279,12 @@ private function getCommandHelp(): string return <<<'HELP' The %command.name% command creates new users and saves them in the database: - php %command.full_name% email password - -By default the command creates regular users. To create administrator users, -add the --admin option: - - php %command.full_name% email password --admin + php %command.full_name% email password fullname role tenants -If you omit any of the two required arguments, the command will ask you to +If you omit any of the required arguments, the command will ask you to provide the missing values: - # command will ask you for the password + # command will ask you for the password etc. php %command.full_name% email # command will ask you for all arguments diff --git a/src/Utils/CommandInputValidator.php b/src/Utils/CommandInputValidator.php index 339e309d..8cbc0e38 100644 --- a/src/Utils/CommandInputValidator.php +++ b/src/Utils/CommandInputValidator.php @@ -11,6 +11,7 @@ namespace App\Utils; +use App\Repository\TenantRepository; use Symfony\Component\Console\Exception\InvalidArgumentException; use function Symfony\Component\String\u; @@ -23,6 +24,12 @@ */ class CommandInputValidator { + public const ALLOWED_USER_ROLES = ['editor', 'admin']; + + public function __construct( + private TenantRepository $tenantRepository, + ) {} + public function validateUsername(?string $username): string { if (empty($username)) { @@ -83,4 +90,38 @@ public function validateFullName(?string $fullName): string return $fullName; } + + public function validateRole(?string $role): string + { + if (empty($role)) { + throw new InvalidArgumentException('The role can not be empty.'); + } + + if (!in_array($role, self::ALLOWED_USER_ROLES)) { + throw new InvalidArgumentException('Unknown role: '.$role); + } + + return $role; + } + + public function validateTenantKeys(?array $tenantKeys): array + { + if (empty($tenantKeys)) { + throw new InvalidArgumentException('The user must belong to at least one tenant.'); + } + + $unknownKeys = []; + foreach ($tenantKeys as $tenantKey) { + $tenant = $this->tenantRepository->findOneBy(['tenantKey' => $tenantKey]); + if (null === $tenant) { + $unknownKeys[] = $tenantKey; + } + } + + if (0 !== \count($unknownKeys)) { + throw new InvalidArgumentException(sprintf('Unknown tenant keys: %s.', implode(', ', $unknownKeys))); + } + + return $tenantKeys; + } }