Skip to content

Commit

Permalink
abstract out common security logic
Browse files Browse the repository at this point in the history
  • Loading branch information
jrushlow committed May 23, 2023
1 parent ae32f23 commit c8e960d
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 172 deletions.
62 changes: 61 additions & 1 deletion src/Maker/Security/AbstractSecurityMaker.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,22 @@

namespace Symfony\Bundle\MakerBundle\Maker\Security;

use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
use Symfony\Bundle\MakerBundle\ConsoleStyle;
use Symfony\Bundle\MakerBundle\DependencyBuilder;
use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
use Symfony\Bundle\MakerBundle\FileManager;
use Symfony\Bundle\MakerBundle\Maker\AbstractMaker;
use Symfony\Bundle\MakerBundle\Security\InteractiveSecurityHelper;
use Symfony\Bundle\MakerBundle\Security\SecurityConfigUpdater;
use Symfony\Bundle\MakerBundle\Security\SecurityControllerBuilder;
use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator;
use Symfony\Bundle\MakerBundle\Validator;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Process\Process;
use Symfony\Component\Yaml\Yaml;

/**
* @author Jesse Rushlow <jr@rushlow.dev>
Expand All @@ -21,12 +35,58 @@
*/
abstract class AbstractSecurityMaker extends AbstractMaker
{
public function runFixer(string $templateFilePath): void
protected const SECURITY_CONFIG_PATH = 'config/packages/security.yaml';

protected YamlSourceManipulator $ysm;
protected string $securityControllerName;
protected string $firewallToUpdate;
protected string $userClass;
protected string $userNameField;
protected bool $willLogout;

public function __construct(
protected FileManager $fileManager,
protected SecurityConfigUpdater $securityConfigUpdater,
protected SecurityControllerBuilder $securityControllerBuilder,
) {
}

protected function runFixer(string $templateFilePath): void
{
$fixerPath = \dirname(__DIR__, 2).'/bin/php-cs-fixer-v3.13.0.phar';
$configPath = \dirname(__DIR__, 2).'/Resources/test/.php_cs.test';

$process = Process::fromShellCommandline(sprintf('php %s --config=%s --using-cache=no fix %s', $fixerPath, $configPath, $templateFilePath));
$process->run();
}

public function configureDependencies(DependencyBuilder $dependencies): void
{
$dependencies->addClassDependency(SecurityBundle::class, 'security');
$dependencies->addClassDependency(Process::class, 'process');
$dependencies->addClassDependency(Yaml::class, 'yaml');
$dependencies->addClassDependency(DoctrineBundle::class, 'orm');
}

public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
{
if (!$this->fileManager->fileExists(self::SECURITY_CONFIG_PATH)) {
throw new RuntimeCommandException(sprintf('The file "%s" does not exist. PHP & XML configuration formats are currently not supported.', self::SECURITY_CONFIG_PATH));
}

$this->securityControllerName = $io->ask(
'Choose a name for the controller class (e.g. <fg=yellow>ApiLoginController</>)',
'ApiLoginController',
[Validator::class, 'validateClassName']
);

$this->ysm = new YamlSourceManipulator($this->fileManager->getFileContents(self::SECURITY_CONFIG_PATH));
$securityData = $this->ysm->getData();

$securityHelper = new InteractiveSecurityHelper();
$this->firewallToUpdate = $securityHelper->guessFirewallName($io, $securityData);
$this->userClass = $securityHelper->guessUserClass($io, $securityData['security']['providers']);
$this->userNameField = $securityHelper->guessUserNameField($io, $this->userClass, $securityData['security']['providers']);
$this->willLogout = $io->confirm('Do you want to generate a \'/logout\' URL?');
}
}
57 changes: 4 additions & 53 deletions src/Maker/Security/MakeFormLogin.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,22 @@

namespace Symfony\Bundle\MakerBundle\Maker\Security;

use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\MakerBundle\ConsoleStyle;
use Symfony\Bundle\MakerBundle\DependencyBuilder;
use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
use Symfony\Bundle\MakerBundle\FileManager;
use Symfony\Bundle\MakerBundle\Generator;
use Symfony\Bundle\MakerBundle\InputConfiguration;
use Symfony\Bundle\MakerBundle\Maker\AbstractMaker;
use Symfony\Bundle\MakerBundle\Security\InteractiveSecurityHelper;
use Symfony\Bundle\MakerBundle\Security\SecurityConfigUpdater;
use Symfony\Bundle\MakerBundle\Security\SecurityControllerBuilder;
use Symfony\Bundle\MakerBundle\Str;
use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator;
use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator;
use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator;
use Symfony\Bundle\MakerBundle\Validator;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Bundle\TwigBundle\TwigBundle;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Component\Yaml\Yaml;

/**
* Generate Form Login Security using SecurityBundle's Authenticator.
Expand All @@ -46,22 +37,8 @@
*
* @internal
*/
final class MakeFormLogin extends AbstractMaker
final class MakeFormLogin extends AbstractSecurityMaker
{
private const SECURITY_CONFIG_PATH = 'config/packages/security.yaml';
private YamlSourceManipulator $ysm;
private string $controllerName;
private string $firewallToUpdate;
private string $userNameField;
private bool $willLogout;

public function __construct(
private FileManager $fileManager,
private SecurityConfigUpdater $securityConfigUpdater,
private SecurityControllerBuilder $securityControllerBuilder,
) {
}

public static function getCommandName(): string
{
return 'make:security:form-login';
Expand All @@ -79,46 +56,20 @@ public static function getCommandDescription(): string

public function configureDependencies(DependencyBuilder $dependencies): void
{
$dependencies->addClassDependency(
SecurityBundle::class,
'security'
);

$dependencies->addClassDependency(TwigBundle::class, 'twig');

// needed to update the YAML files
$dependencies->addClassDependency(
Yaml::class,
'yaml'
);

$dependencies->addClassDependency(DoctrineBundle::class, 'orm');
parent::configureDependencies($dependencies);
}

public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
{
if (!$this->fileManager->fileExists(self::SECURITY_CONFIG_PATH)) {
throw new RuntimeCommandException(sprintf('The file "%s" does not exist. PHP & XML configuration formats are currently not supported.', self::SECURITY_CONFIG_PATH));
}
parent::interact($input, $io, $command);

$this->ysm = new YamlSourceManipulator($this->fileManager->getFileContents(self::SECURITY_CONFIG_PATH));
$securityData = $this->ysm->getData();

if (!isset($securityData['security']['providers']) || !$securityData['security']['providers']) {
throw new RuntimeCommandException('To generate a form login authentication, you must configure at least one entry under "providers" in "security.yaml".');
}

$this->controllerName = $io->ask(
'Choose a name for the controller class (e.g. <fg=yellow>SecurityController</>)',
'SecurityController',
[Validator::class, 'validateClassName']
);

$securityHelper = new InteractiveSecurityHelper();
$this->firewallToUpdate = $securityHelper->guessFirewallName($io, $securityData);
$userClass = $securityHelper->guessUserClass($io, $securityData['security']['providers']);
$this->userNameField = $securityHelper->guessUserNameField($io, $userClass, $securityData['security']['providers']);
$this->willLogout = $io->confirm('Do you want to generate a \'/logout\' URL?');
}

public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
Expand All @@ -130,7 +81,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
AuthenticationUtils::class,
]);

$controllerNameDetails = $generator->createClassNameDetails($this->controllerName, 'Controller\\', 'Controller');
$controllerNameDetails = $generator->createClassNameDetails($this->securityControllerName, 'Controller\\', 'Controller');
$templatePath = strtolower($controllerNameDetails->getRelativeNameWithoutSuffix());

$controllerPath = $generator->generateController(
Expand Down
134 changes: 16 additions & 118 deletions src/Maker/Security/MakeJsonLogin.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,14 @@

namespace Symfony\Bundle\MakerBundle\Maker\Security;

use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
use Symfony\Bundle\MakerBundle\ConsoleStyle;
use Symfony\Bundle\MakerBundle\DependencyBuilder;
use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
use Symfony\Bundle\MakerBundle\FileManager;
use Symfony\Bundle\MakerBundle\Generator;
use Symfony\Bundle\MakerBundle\InputConfiguration;
use Symfony\Bundle\MakerBundle\Security\InteractiveSecurityHelper;
use Symfony\Bundle\MakerBundle\Security\SecurityConfigUpdater;
use Symfony\Bundle\MakerBundle\Security\SecurityControllerBuilder;
use Symfony\Bundle\MakerBundle\Str;
use Symfony\Bundle\MakerBundle\Util\ClassNameDetails;
use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator;
use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator;
use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator;
use Symfony\Bundle\MakerBundle\Validator;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Process\Process;
use Symfony\Component\Yaml\Yaml;

/**
* Generate Form Login Security using SecurityBundle's Authenticator.
Expand All @@ -44,21 +31,6 @@
*/
final class MakeJsonLogin extends AbstractSecurityMaker
{
private const SECURITY_CONFIG_PATH = 'config/packages/security.yaml';
private YamlSourceManipulator $ysm;
private string $controllerName;
private string $firewallToUpdate;
private string $userClass;
private string $userNameField;
private bool $willLogout;

public function __construct(
private FileManager $fileManager,
private SecurityConfigUpdater $securityConfigUpdater,
private SecurityControllerBuilder $securityControllerBuilder,
) {
}

public static function getCommandName(): string
{
return 'make:security:json-login';
Expand All @@ -74,49 +46,6 @@ public static function getCommandDescription(): string
return 'Generate the code needed for the json_login authenticator';
}

public function configureDependencies(DependencyBuilder $dependencies): void
{
$dependencies->addClassDependency(SecurityBundle::class, 'security');

$dependencies->addClassDependency(Process::class, 'process');
//
// $dependencies->addClassDependency(TwigBundle::class, 'twig');
//
// // needed to update the YAML files
// $dependencies->addClassDependency(
// Yaml::class,
// 'yaml'
// );
//
$dependencies->addClassDependency(DoctrineBundle::class, 'orm');
}

public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
{
if (!$this->fileManager->fileExists(self::SECURITY_CONFIG_PATH)) {
throw new RuntimeCommandException(sprintf('The file "%s" does not exist. PHP & XML configuration formats are currently not supported.', self::SECURITY_CONFIG_PATH));
}

$this->ysm = new YamlSourceManipulator($this->fileManager->getFileContents(self::SECURITY_CONFIG_PATH));
$securityData = $this->ysm->getData();

// if (!isset($securityData['security']['providers']) || !$securityData['security']['providers']) {
// throw new RuntimeCommandException('To generate a json login authentication, you must configure at least one entry under "providers" in "security.yaml".');
// }

$this->controllerName = $io->ask(
'Choose a name for the controller class (e.g. <fg=yellow>ApiLoginController</>)',
'ApiLoginController',
[Validator::class, 'validateClassName']
);

$securityHelper = new InteractiveSecurityHelper();
$this->firewallToUpdate = $securityHelper->guessFirewallName($io, $securityData);
$this->userClass = $securityHelper->guessUserClass($io, $securityData['security']['providers']);
$this->userNameField = $securityHelper->guessUserNameField($io, $this->userClass, $securityData['security']['providers']);
$this->willLogout = $io->confirm('Do you want to generate a \'/logout\' URL?');
}

public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
{
$userClassDetails = new ClassNameDetails($this->userClass, '');
Expand All @@ -130,73 +59,42 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
\Symfony\Component\Security\Http\Attribute\CurrentUser::class,
]);

$controllerNameDetails = $generator->createClassNameDetails($this->controllerName, 'Controller\\', 'Controller');
$controllerNameDetails = $generator->createClassNameDetails($this->securityControllerName, 'Controller\\', 'Controller');

$controllerPath = $this->fileManager->getRelativePathForFutureClass($controllerNameDetails->getFullName());

$controllerExists = $this->fileManager->fileExists($controllerPath);

if ($controllerExists) {
$manipulator = new ClassSourceManipulator(file_get_contents($controllerPath));
} else {
$generator->generateController($controllerNameDetails->getFullName(), 'security/jsonLogin/EmptyController.tpl.php', [
'use_statements' => $useStatements,
'controller_name' => $controllerNameDetails->getShortName(),
]);

$generator->writeChanges();

$manipulator = new ClassSourceManipulator(file_get_contents($controllerPath));
if (!$controllerExists) {
$generator->generateController(
$controllerNameDetails->getFullName(),
'security/jsonLogin/EmptyController.tpl.php',
[
'use_statements' => $useStatements,
'controller_name' => $controllerNameDetails->getShortName(),
]
);
}

// if ($controllerExists) {
// $manipulator = new ClassSourceManipulator(file_get_contents($controllerPath));
$controllerSource = $controllerExists ? file_get_contents($controllerPath) : $generator->getFileContentsForPendingOperation($controllerPath);

$this->securityControllerBuilder->addJsonLoginMethod($manipulator, $userClassDetails);
$manipulator = new ClassSourceManipulator($controllerSource);

$generator->dumpFile($controllerPath, $manipulator->getSourceCode());
$generator->writeChanges();
$this->securityControllerBuilder->addJsonLoginMethod($manipulator, $userClassDetails);

$this->runFixer($controllerPath);
// } else {
// $controllerPath = $generator->generateController(
// $controllerNameDetails->getFullName(),
// 'security/jsonLogin/LoginController.tpl.php',
// [
// 'use_statements' => $useStatements,
// 'controller_name' => $controllerNameDetails->getShortName(),
// 'user_class' => $userClassDetails->getShortName(),
// ]
// );
// }
$securityData = $this->securityConfigUpdater->updateForJsonLogin($this->ysm->getContents(), $this->firewallToUpdate, 'api_login');

if ($this->willLogout) {
// $manipulator = new ClassSourceManipulator($generator->getFileContentsForPendingOperation($controllerPath));

$this->securityControllerBuilder->addLogoutMethod($manipulator);

$generator->dumpFile($controllerPath, $manipulator->getSourceCode());
}
//
// $generator->generateTemplate(
// sprintf('%s/login.html.twig', $templatePath),
// 'security/formLogin/login_form.tpl.php',
// [
// 'logout_setup' => $this->willLogout,
// 'username_label' => Str::asHumanWords($this->userNameField),
// 'username_is_email' => false !== stripos($this->userNameField, 'email'),
// ]
// );
//
$securityData = $this->securityConfigUpdater->updateForJsonLogin($this->ysm->getContents(), $this->firewallToUpdate, 'api_login');

if ($this->willLogout) {
$securityData = $this->securityConfigUpdater->updateForLogout($securityData, $this->firewallToUpdate);
}

$generator->dumpFile(self::SECURITY_CONFIG_PATH, $securityData);
$generator->dumpFile($controllerPath, $manipulator->getSourceCode());

$generator->writeChanges();
$this->runFixer($controllerPath);

$this->writeSuccessMessage($io);

Expand Down

0 comments on commit c8e960d

Please sign in to comment.