Skip to content

Commit

Permalink
feature #1104 [make:auth] drop guard support and legacy code cleanup …
Browse files Browse the repository at this point in the history
…(jrushlow)

This PR was squashed before being merged into the 1.0-dev branch.

Discussion
----------

[make:auth] drop guard support and legacy code cleanup

- drops support for the Guard Security System
- deprecates `Generator::getControllerForBaseClass`
- fixes #1082

Commits
-------

324f6f6 [make:auth] drop guard support and legacy code cleanup
  • Loading branch information
weaverryan committed Apr 26, 2022
2 parents 5052c21 + 324f6f6 commit 169d46f
Show file tree
Hide file tree
Showing 51 changed files with 214 additions and 784 deletions.
7 changes: 5 additions & 2 deletions src/Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
namespace Symfony\Bundle\MakerBundle;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
use Symfony\Bundle\MakerBundle\Util\ClassNameDetails;
use Symfony\Bundle\MakerBundle\Util\PhpCompatUtil;
Expand Down Expand Up @@ -229,7 +228,6 @@ public function generateController(string $controllerClassName, string $controll
$parameters +
[
'generator' => $this->templateComponentGenerator,
'parent_class_name' => static::getControllerBaseClass()->getShortName(),
]
);
}
Expand All @@ -246,8 +244,13 @@ public function generateTemplate(string $targetPath, string $templateName, array
);
}

/**
* @deprecated MakerBundle only supports AbstractController::class. This method will be removed in the future.
*/
public static function getControllerBaseClass(): ClassNameDetails
{
trigger_deprecation('symfony/maker-bundle', 'v1.41.0', 'MakerBundle only supports AbstractController. This method will be removed in the future.');

return new ClassNameDetails(AbstractController::class, '\\');
}
}
160 changes: 64 additions & 96 deletions src/Maker/MakeAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Symfony\Bundle\MakerBundle\Maker;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\MakerBundle\ConsoleStyle;
use Symfony\Bundle\MakerBundle\DependencyBuilder;
use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper;
Expand All @@ -23,6 +24,7 @@
use Symfony\Bundle\MakerBundle\Security\SecurityControllerBuilder;
use Symfony\Bundle\MakerBundle\Str;
use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator;
use Symfony\Bundle\MakerBundle\Util\TemplateComponentGenerator;
use Symfony\Bundle\MakerBundle\Util\YamlManipulationFailedException;
use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator;
use Symfony\Bundle\MakerBundle\Validator;
Expand All @@ -33,12 +35,22 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
use Symfony\Component\Yaml\Yaml;

/**
Expand All @@ -53,17 +65,11 @@ final class MakeAuthenticator extends AbstractMaker
private const AUTH_TYPE_FORM_LOGIN = 'form-login';

private $fileManager;

private $configUpdater;

private $generator;

private $doctrineHelper;

private $securityControllerBuilder;

private $useSecurity52 = false;

public function __construct(FileManager $fileManager, SecurityConfigUpdater $configUpdater, Generator $generator, DoctrineHelper $doctrineHelper, SecurityControllerBuilder $securityControllerBuilder)
{
$this->fileManager = $fileManager;
Expand All @@ -83,27 +89,22 @@ public static function getCommandDescription(): string
return 'Creates a Guard authenticator of different flavors';
}

public function configureCommand(Command $command, InputConfiguration $inputConfig)
public function configureCommand(Command $command, InputConfiguration $inputConfig): void
{
$command
->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeAuth.txt'));
}

public function interact(InputInterface $input, ConsoleStyle $io, Command $command)
public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
{
if (!$this->fileManager->fileExists($path = 'config/packages/security.yaml')) {
throw new RuntimeCommandException('The file "config/packages/security.yaml" does not exist. This command requires that file to exist so that it can be updated.');
}
$manipulator = new YamlSourceManipulator($this->fileManager->getFileContents($path));
$securityData = $manipulator->getData();

// Determine if we should use new security features introduced in Symfony 5.2
if ($securityData['security']['enable_authenticator_manager'] ?? false) {
$this->useSecurity52 = true;
}

if ($this->useSecurity52 && !class_exists(UserBadge::class)) {
throw new RuntimeCommandException('MakerBundle does not support generating authenticators using the new authenticator system before symfony/security-bundle 5.2. Please upgrade to 5.2 and try again.');
if (!($securityData['security']['enable_authenticator_manager'] ?? false)) {
throw new RuntimeCommandException('MakerBundle only supports the new authenticator based security system. See https://symfony.com/doc/current/security.html');
}

// authenticator type
Expand All @@ -124,14 +125,8 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma

if (self::AUTH_TYPE_FORM_LOGIN === $input->getArgument('authenticator-type')) {
$neededDependencies = [TwigBundle::class => 'twig'];
$missingPackagesMessage = 'Twig must be installed to display the login form.';

if (Kernel::VERSION_ID < 40100) {
$neededDependencies[Form::class] = 'symfony/form';
$missingPackagesMessage = 'Twig and symfony/form must be installed to display the login form';
}
$missingPackagesMessage = $this->addDependencies($neededDependencies, 'Twig must be installed to display the login form.');

$missingPackagesMessage = $this->addDependencies($neededDependencies, $missingPackagesMessage);
if ($missingPackagesMessage) {
throw new RuntimeCommandException($missingPackagesMessage);
}
Expand Down Expand Up @@ -161,13 +156,6 @@ function ($answer) {

$command->addOption('entry-point', null, InputOption::VALUE_OPTIONAL);

if (!$this->useSecurity52) {
$input->setOption(
'entry-point',
$interactiveSecurityHelper->guessEntryPoint($io, $securityData, $input->getArgument('authenticator-class'), $firewallName)
);
}

if (self::AUTH_TYPE_FORM_LOGIN === $input->getArgument('authenticator-type')) {
$command->addArgument('controller-class', InputArgument::REQUIRED);
$input->setArgument(
Expand Down Expand Up @@ -202,7 +190,7 @@ function ($answer) {
}
}

public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator)
public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
{
$manipulator = new YamlSourceManipulator($this->fileManager->getFileContents('config/packages/security.yaml'));
$securityData = $manipulator->getData();
Expand All @@ -220,7 +208,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen

$entryPoint = $input->getOption('entry-point');

if ($this->useSecurity52 && self::AUTH_TYPE_FORM_LOGIN !== $input->getArgument('authenticator-type')) {
if (self::AUTH_TYPE_FORM_LOGIN !== $input->getArgument('authenticator-type')) {
$entryPoint = false;
}

Expand All @@ -230,8 +218,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
$input->getOption('firewall-name'),
$entryPoint,
$input->getArgument('authenticator-class'),
$input->hasArgument('logout-setup') ? $input->getArgument('logout-setup') : false,
$this->useSecurity52
$input->hasArgument('logout-setup') ? $input->getArgument('logout-setup') : false
);
$generator->dumpFile($path, $newYaml);
$securityYamlUpdated = true;
Expand Down Expand Up @@ -262,45 +249,64 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
);
}

private function generateAuthenticatorClass(array $securityData, string $authenticatorType, string $authenticatorClass, $userClass, $userNameField)
private function generateAuthenticatorClass(array $securityData, string $authenticatorType, string $authenticatorClass, $userClass, $userNameField): void
{
$useStatements = [
Request::class,
Response::class,
TokenInterface::class,
Passport::class,
];

// generate authenticator class
if (self::AUTH_TYPE_EMPTY_AUTHENTICATOR === $authenticatorType) {
$emptyAuthUseStatements = array_merge($useStatements, [
AuthenticationException::class,
AbstractAuthenticator::class,
]);

$this->generator->generateClass(
$authenticatorClass,
sprintf('authenticator/%sEmptyAuthenticator.tpl.php', $this->useSecurity52 ? 'Security52' : ''),
[
'provider_key_type_hint' => $this->getGuardProviderKeyTypeHint(),
'use_legacy_passport_interface' => $this->shouldUseLegacyPassportInterface(),
]
'authenticator/EmptyAuthenticator.tpl.php',
['use_statements' => TemplateComponentGenerator::generateUseStatements($emptyAuthUseStatements)]
);

return;
}

$useStatements = array_merge($useStatements, [
RedirectResponse::class,
UrlGeneratorInterface::class,
Security::class,
AbstractLoginFormAuthenticator::class,
CsrfTokenBadge::class,
UserBadge::class,
PasswordCredentials::class,
TargetPathTrait::class,
]);

$userClassNameDetails = $this->generator->createClassNameDetails(
'\\'.$userClass,
'Entity\\'
);

$this->generator->generateClass(
$authenticatorClass,
sprintf('authenticator/%sLoginFormAuthenticator.tpl.php', $this->useSecurity52 ? 'Security52' : ''),
'authenticator/LoginFormAuthenticator.tpl.php',
[
'use_statements' => TemplateComponentGenerator::generateUseStatements($useStatements),
'user_fully_qualified_class_name' => trim($userClassNameDetails->getFullName(), '\\'),
'user_class_name' => $userClassNameDetails->getShortName(),
'username_field' => $userNameField,
'username_field_label' => Str::asHumanWords($userNameField),
'username_field_var' => Str::asLowerCamelCase($userNameField),
'user_needs_encoder' => $this->userClassHasEncoder($securityData, $userClass),
'user_is_entity' => $this->doctrineHelper->isClassAMappedEntity($userClass),
'provider_key_type_hint' => $this->getGuardProviderKeyTypeHint(),
'use_legacy_passport_interface' => $this->shouldUseLegacyPassportInterface(),
]
);
}

private function generateFormLoginFiles(string $controllerClass, string $userNameField, bool $logoutSetup)
private function generateFormLoginFiles(string $controllerClass, string $userNameField, bool $logoutSetup): void
{
$controllerClassNameDetails = $this->generator->createClassNameDetails(
$controllerClass,
Expand All @@ -309,9 +315,16 @@ private function generateFormLoginFiles(string $controllerClass, string $userNam
);

if (!class_exists($controllerClassNameDetails->getFullName())) {
$useStatements = [
AbstractController::class,
Route::class,
AuthenticationUtils::class,
];

$controllerPath = $this->generator->generateController(
$controllerClassNameDetails->getFullName(),
'authenticator/EmptySecurityController.tpl.php'
'authenticator/EmptySecurityController.tpl.php',
['use_statements' => TemplateComponentGenerator::generateUseStatements($useStatements)]
);

$controllerSourceCode = $this->generator->getFileContentsForPendingOperation($controllerPath);
Expand Down Expand Up @@ -358,8 +371,7 @@ private function generateNextMessage(bool $securityYamlUpdated, string $authenti
'main',
null,
$authenticatorClass,
$logoutSetup,
$this->useSecurity52
$logoutSetup
);
$nextTexts[] = "- Your <info>security.yaml</info> could not be updated automatically. You'll need to add the following config manually:\n\n".$yamlExample;
}
Expand All @@ -371,11 +383,6 @@ private function generateNextMessage(bool $securityYamlUpdated, string $authenti
$nextTexts[] = sprintf('- Review <info>%s::getUser()</info> to make sure it matches your needs.', $authenticatorClass);
}

// this only applies to Guard authentication AND if the user does not have a hasher configured
if (!$this->useSecurity52 && !$this->userClassHasEncoder($securityData, $userClass)) {
$nextTexts[] = sprintf('- Check the user\'s password in <info>%s::checkCredentials()</info>.', $authenticatorClass);
}

$nextTexts[] = '- Review & adapt the login template: <info>'.$this->fileManager->getPathForTemplate('security/login.html.twig').'</info>.';
}

Expand All @@ -396,7 +403,7 @@ private function userClassHasEncoder(array $securityData, string $userClass): bo
return $userNeedsEncoder;
}

public function configureDependencies(DependencyBuilder $dependencies, InputInterface $input = null)
public function configureDependencies(DependencyBuilder $dependencies, InputInterface $input = null): void
{
$dependencies->addClassDependency(
SecurityBundle::class,
Expand All @@ -409,43 +416,4 @@ public function configureDependencies(DependencyBuilder $dependencies, InputInte
'yaml'
);
}

/**
* Calculates the type-hint used for the $provider argument (string or nothing) for Guard.
*/
private function getGuardProviderKeyTypeHint(): string
{
// doesn't matter: this only applies to non-Guard authenticators
if (!class_exists(AbstractFormLoginAuthenticator::class)) {
return '';
}

$reflectionMethod = new \ReflectionMethod(AbstractFormLoginAuthenticator::class, 'onAuthenticationSuccess');
$type = $reflectionMethod->getParameters()[2]->getType();

if (!$type instanceof \ReflectionNamedType) {
return '';
}

return sprintf('%s ', $type->getName());
}

private function shouldUseLegacyPassportInterface(): bool
{
// only applies to new authenticator security
if (!$this->useSecurity52) {
return false;
}

// legacy: checking for Symfony 5.2 & 5.3 before PassportInterface deprecation
$class = new \ReflectionClass(AuthenticatorInterface::class);
$method = $class->getMethod('authenticate');

// 5.4 where return type is temporarily removed
if (!$method->getReturnType()) {
return false;
}

return PassportInterface::class === $method->getReturnType()->getName();
}
}
Loading

0 comments on commit 169d46f

Please sign in to comment.