welcome back to dyb-tech
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony MakerBundle package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\MakerBundle\Security;
|
||||
|
||||
use Symfony\Bundle\MakerBundle\Str;
|
||||
use Symfony\Bundle\MakerBundle\Validator;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class InteractiveSecurityHelper
|
||||
{
|
||||
public function guessFirewallName(SymfonyStyle $io, array $securityData, string $questionText = null): string
|
||||
{
|
||||
$realFirewalls = array_filter(
|
||||
$securityData['security']['firewalls'] ?? [],
|
||||
static function ($item) {
|
||||
return !isset($item['security']) || true === $item['security'];
|
||||
}
|
||||
);
|
||||
|
||||
if (0 === \count($realFirewalls)) {
|
||||
return 'main';
|
||||
}
|
||||
|
||||
if (1 === \count($realFirewalls)) {
|
||||
return key($realFirewalls);
|
||||
}
|
||||
|
||||
return $io->choice(
|
||||
$questionText ?? 'Which firewall do you want to update?',
|
||||
array_keys($realFirewalls),
|
||||
key($realFirewalls)
|
||||
);
|
||||
}
|
||||
|
||||
public function guessUserClass(SymfonyStyle $io, array $providers, string $questionText = null): string
|
||||
{
|
||||
if (1 === \count($providers) && isset(current($providers)['entity'])) {
|
||||
$entityProvider = current($providers);
|
||||
|
||||
return $entityProvider['entity']['class'];
|
||||
}
|
||||
|
||||
return $io->ask(
|
||||
$questionText ?? 'Enter the User class that you want to authenticate (e.g. <fg=yellow>App\\Entity\\User</>)',
|
||||
$this->guessUserClassDefault(),
|
||||
[Validator::class, 'classIsUserInterface']
|
||||
);
|
||||
}
|
||||
|
||||
private function guessUserClassDefault(): string
|
||||
{
|
||||
if (class_exists('App\\Entity\\User') && isset(class_implements('App\\Entity\\User')[UserInterface::class])) {
|
||||
return 'App\\Entity\\User';
|
||||
}
|
||||
|
||||
if (class_exists('App\\Security\\User') && isset(class_implements('App\\Security\\User')[UserInterface::class])) {
|
||||
return 'App\\Security\\User';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function guessUserNameField(SymfonyStyle $io, string $userClass, array $providers): string
|
||||
{
|
||||
if (1 === \count($providers) && isset(current($providers)['entity']) && isset(current($providers)['entity']['property'])) {
|
||||
$entityProvider = current($providers);
|
||||
|
||||
return $entityProvider['entity']['property'];
|
||||
}
|
||||
|
||||
if (property_exists($userClass, 'email') && !property_exists($userClass, 'username')) {
|
||||
return 'email';
|
||||
}
|
||||
|
||||
if (!property_exists($userClass, 'email') && property_exists($userClass, 'username')) {
|
||||
return 'username';
|
||||
}
|
||||
|
||||
$classProperties = [];
|
||||
$reflectionClass = new \ReflectionClass($userClass);
|
||||
foreach ($reflectionClass->getProperties() as $property) {
|
||||
$classProperties[] = $property->name;
|
||||
}
|
||||
|
||||
if (empty($classProperties)) {
|
||||
throw new \LogicException(sprintf('No properties were found in "%s" entity', $userClass));
|
||||
}
|
||||
|
||||
return $io->choice(
|
||||
sprintf('Which field on your <fg=yellow>%s</> class will people enter when logging in?', $userClass),
|
||||
$classProperties,
|
||||
property_exists($userClass, 'username') ? 'username' : (property_exists($userClass, 'email') ? 'email' : null)
|
||||
);
|
||||
}
|
||||
|
||||
public function guessEmailField(SymfonyStyle $io, string $userClass): string
|
||||
{
|
||||
if (property_exists($userClass, 'email')) {
|
||||
return 'email';
|
||||
}
|
||||
|
||||
$classProperties = [];
|
||||
$reflectionClass = new \ReflectionClass($userClass);
|
||||
foreach ($reflectionClass->getProperties() as $property) {
|
||||
$classProperties[] = $property->name;
|
||||
}
|
||||
|
||||
return $io->choice(
|
||||
sprintf('Which field on your <fg=yellow>%s</> class holds the email address?', $userClass),
|
||||
$classProperties
|
||||
);
|
||||
}
|
||||
|
||||
public function guessPasswordField(SymfonyStyle $io, string $userClass): string
|
||||
{
|
||||
if (property_exists($userClass, 'password')) {
|
||||
return 'password';
|
||||
}
|
||||
|
||||
$classProperties = [];
|
||||
$reflectionClass = new \ReflectionClass($userClass);
|
||||
foreach ($reflectionClass->getProperties() as $property) {
|
||||
$classProperties[] = $property->name;
|
||||
}
|
||||
|
||||
return $io->choice(
|
||||
sprintf('Which field on your <fg=yellow>%s</> class holds the encoded password?', $userClass),
|
||||
$classProperties
|
||||
);
|
||||
}
|
||||
|
||||
public function getAuthenticatorClasses(array $firewallData): array
|
||||
{
|
||||
if (isset($firewallData['guard'])) {
|
||||
return array_filter($firewallData['guard']['authenticators'] ?? [], static function ($authenticator) {
|
||||
return class_exists($authenticator);
|
||||
});
|
||||
}
|
||||
|
||||
if (isset($firewallData['custom_authenticator'])) {
|
||||
$authenticators = $firewallData['custom_authenticator'];
|
||||
if (\is_string($authenticators)) {
|
||||
$authenticators = [$authenticators];
|
||||
}
|
||||
|
||||
return array_filter($authenticators, static function ($authenticator) {
|
||||
return class_exists($authenticator);
|
||||
});
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public function guessPasswordSetter(SymfonyStyle $io, string $userClass): string
|
||||
{
|
||||
if (null === ($methodChoices = $this->methodNameGuesser($userClass, 'setPassword'))) {
|
||||
return 'setPassword';
|
||||
}
|
||||
|
||||
return $io->choice(
|
||||
sprintf('Which method on your <fg=yellow>%s</> class can be used to set the encoded password (e.g. setPassword())?', $userClass),
|
||||
$methodChoices
|
||||
);
|
||||
}
|
||||
|
||||
public function guessEmailGetter(SymfonyStyle $io, string $userClass, string $emailPropertyName): string
|
||||
{
|
||||
$supposedEmailMethodName = sprintf('get%s', Str::asCamelCase($emailPropertyName));
|
||||
|
||||
if (null === ($methodChoices = $this->methodNameGuesser($userClass, $supposedEmailMethodName))) {
|
||||
return $supposedEmailMethodName;
|
||||
}
|
||||
|
||||
return $io->choice(
|
||||
sprintf('Which method on your <fg=yellow>%s</> class can be used to get the email address (e.g. getEmail())?', $userClass),
|
||||
$methodChoices
|
||||
);
|
||||
}
|
||||
|
||||
public function guessIdGetter(SymfonyStyle $io, string $userClass): string
|
||||
{
|
||||
if (null === ($methodChoices = $this->methodNameGuesser($userClass, 'getId'))) {
|
||||
return 'getId';
|
||||
}
|
||||
|
||||
return $io->choice(
|
||||
sprintf('Which method on your <fg=yellow>%s</> class can be used to get the unique user identifier (e.g. getId())?', $userClass),
|
||||
$methodChoices
|
||||
);
|
||||
}
|
||||
|
||||
private function methodNameGuesser(string $className, string $suspectedMethodName): ?array
|
||||
{
|
||||
$reflectionClass = new \ReflectionClass($className);
|
||||
|
||||
if ($reflectionClass->hasMethod($suspectedMethodName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$classMethods = [];
|
||||
|
||||
foreach ($reflectionClass->getMethods() as $method) {
|
||||
$classMethods[] = $method->name;
|
||||
}
|
||||
|
||||
return $classMethods;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony MakerBundle package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\MakerBundle\Security;
|
||||
|
||||
use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator;
|
||||
use Symfony\Component\HttpKernel\Log\Logger;
|
||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
|
||||
/**
|
||||
* @author Ryan Weaver <ryan@symfonycasts.com>
|
||||
* @author Jesse Rushlow <jr@rushlow.dev>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class SecurityConfigUpdater
|
||||
{
|
||||
private ?YamlSourceManipulator $manipulator;
|
||||
|
||||
public function __construct(
|
||||
private ?Logger $ysmLogger = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function updateForFormLogin(string $yamlSource, string $firewallToUpdate, string $loginPath, string $checkPath): string
|
||||
{
|
||||
$newData = $this->createYamlSourceManipulator($yamlSource);
|
||||
|
||||
$newData['security']['firewalls'][$firewallToUpdate]['form_login']['login_path'] = $loginPath;
|
||||
$newData['security']['firewalls'][$firewallToUpdate]['form_login']['check_path'] = $checkPath;
|
||||
$newData['security']['firewalls'][$firewallToUpdate]['form_login']['enable_csrf'] = true;
|
||||
|
||||
return $this->getYamlContentsFromData($newData);
|
||||
}
|
||||
|
||||
public function updateForJsonLogin(string $yamlSource, string $firewallToUpdate, string $checkPath): string
|
||||
{
|
||||
$data = $this->createYamlSourceManipulator($yamlSource);
|
||||
|
||||
$data['security']['firewalls'][$firewallToUpdate]['json_login']['check_path'] = $checkPath;
|
||||
|
||||
return $this->getYamlContentsFromData($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates security.yaml contents based on a new User class.
|
||||
*/
|
||||
public function updateForUserClass(string $yamlSource, UserClassConfiguration $userConfig, string $userClass): string
|
||||
{
|
||||
$this->createYamlSourceManipulator($yamlSource);
|
||||
|
||||
$this->updateProviders($userConfig, $userClass);
|
||||
|
||||
if ($userConfig->hasPassword()) {
|
||||
$this->updatePasswordHashers($userClass);
|
||||
}
|
||||
|
||||
$contents = $this->manipulator->getContents();
|
||||
$this->manipulator = null;
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
public function updateForAuthenticator(string $yamlSource, string $firewallName, $chosenEntryPoint, string $authenticatorClass, bool $logoutSetup, bool $supportRememberMe, bool $alwaysRememberMe): string
|
||||
{
|
||||
$this->createYamlSourceManipulator($yamlSource);
|
||||
|
||||
$newData = $this->manipulator->getData();
|
||||
|
||||
if (!isset($newData['security']['firewalls'])) {
|
||||
if ($newData['security']) {
|
||||
$newData['security']['_firewalls'] = $this->manipulator->createEmptyLine();
|
||||
}
|
||||
|
||||
$newData['security']['firewalls'] = [];
|
||||
}
|
||||
|
||||
if (!isset($newData['security']['firewalls'][$firewallName])) {
|
||||
$newData['security']['firewalls'][$firewallName] = ['lazy' => true];
|
||||
}
|
||||
|
||||
$firewall = $newData['security']['firewalls'][$firewallName];
|
||||
|
||||
if (isset($firewall['custom_authenticator'])) {
|
||||
if (\is_array($firewall['custom_authenticator'])) {
|
||||
$firewall['custom_authenticator'][] = $authenticatorClass;
|
||||
} else {
|
||||
$stringValue = $firewall['custom_authenticator'];
|
||||
$firewall['custom_authenticator'] = [];
|
||||
$firewall['custom_authenticator'][] = $stringValue;
|
||||
$firewall['custom_authenticator'][] = $authenticatorClass;
|
||||
}
|
||||
} else {
|
||||
$firewall['custom_authenticator'] = $authenticatorClass;
|
||||
}
|
||||
|
||||
if (!isset($firewall['entry_point']) && $chosenEntryPoint) {
|
||||
$firewall['entry_point_empty_line'] = $this->manipulator->createEmptyLine();
|
||||
$firewall['entry_point_comment'] = $this->manipulator->createCommentLine(
|
||||
' the entry_point start() method determines what happens when an anonymous user accesses a protected page'
|
||||
);
|
||||
$firewall['entry_point'] = $authenticatorClass;
|
||||
}
|
||||
|
||||
if (!isset($firewall['logout']) && $logoutSetup) {
|
||||
$firewall['logout'] = ['path' => 'app_logout'];
|
||||
$firewall['logout'][] = $this->manipulator->createCommentLine(
|
||||
' where to redirect after logout'
|
||||
);
|
||||
$firewall['logout'][] = $this->manipulator->createCommentLine(
|
||||
' target: app_any_route'
|
||||
);
|
||||
}
|
||||
|
||||
if ($supportRememberMe) {
|
||||
if (!isset($firewall['remember_me'])) {
|
||||
$firewall['remember_me_empty_line'] = $this->manipulator->createEmptyLine();
|
||||
$firewall['remember_me'] = [
|
||||
'secret' => '%kernel.secret%',
|
||||
'lifetime' => 604800,
|
||||
'path' => '/',
|
||||
];
|
||||
if (!$alwaysRememberMe) {
|
||||
$firewall['remember_me'][] = $this->manipulator->createCommentLine(' by default, the feature is enabled by checking a checkbox in the');
|
||||
$firewall['remember_me'][] = $this->manipulator->createCommentLine(' login form, uncomment the following line to always enable it.');
|
||||
}
|
||||
} else {
|
||||
$firewall['remember_me']['secret'] ??= '%kernel.secret%';
|
||||
$firewall['remember_me']['lifetime'] ??= 604800;
|
||||
$firewall['remember_me']['path'] ??= '/';
|
||||
}
|
||||
|
||||
if ($alwaysRememberMe) {
|
||||
$firewall['remember_me']['always_remember_me'] = true;
|
||||
} else {
|
||||
$firewall['remember_me'][] = $this->manipulator->createCommentLine('always_remember_me: true');
|
||||
}
|
||||
}
|
||||
|
||||
$newData['security']['firewalls'][$firewallName] = $firewall;
|
||||
|
||||
if (!isset($firewall['logout']) && $logoutSetup) {
|
||||
$this->configureLogout($newData, $firewallName);
|
||||
|
||||
return $this->manipulator->getContents();
|
||||
}
|
||||
|
||||
$this->manipulator->setData($newData);
|
||||
|
||||
return $this->manipulator->getContents();
|
||||
}
|
||||
|
||||
public function updateForLogout(string $yamlSource, string $firewallName): string
|
||||
{
|
||||
$this->createYamlSourceManipulator($yamlSource);
|
||||
|
||||
$this->configureLogout($this->manipulator->getData(), $firewallName);
|
||||
|
||||
return $this->manipulator->getContents();
|
||||
}
|
||||
|
||||
/**
|
||||
* @legacy This can be removed once we deprecate/remove `make:auth`
|
||||
*/
|
||||
private function configureLogout(array $securityData, string $firewallName): void
|
||||
{
|
||||
$securityData['security']['firewalls'][$firewallName]['logout'] = ['path' => 'app_logout'];
|
||||
$securityData['security']['firewalls'][$firewallName]['logout'][] = $this->manipulator->createCommentLine(
|
||||
' where to redirect after logout'
|
||||
);
|
||||
$securityData['security']['firewalls'][$firewallName]['logout'][] = $this->manipulator->createCommentLine(
|
||||
' target: app_any_route'
|
||||
);
|
||||
|
||||
$this->manipulator->setData($securityData);
|
||||
}
|
||||
|
||||
private function createYamlSourceManipulator(string $yamlSource): array
|
||||
{
|
||||
$this->manipulator = new YamlSourceManipulator($yamlSource);
|
||||
|
||||
if (null !== $this->ysmLogger) {
|
||||
$this->manipulator->setLogger($this->ysmLogger);
|
||||
}
|
||||
|
||||
$this->normalizeSecurityYamlFile();
|
||||
|
||||
return $this->manipulator->getData();
|
||||
}
|
||||
|
||||
private function getYamlContentsFromData(array $yamlData): string
|
||||
{
|
||||
$this->manipulator->setData($yamlData);
|
||||
|
||||
return $this->manipulator->getContents();
|
||||
}
|
||||
|
||||
private function normalizeSecurityYamlFile(): void
|
||||
{
|
||||
if (!isset($this->manipulator->getData()['security'])) {
|
||||
$newData = $this->manipulator->getData();
|
||||
$newData['security'] = [];
|
||||
$this->manipulator->setData($newData);
|
||||
}
|
||||
}
|
||||
|
||||
private function updateProviders(UserClassConfiguration $userConfig, string $userClass): void
|
||||
{
|
||||
$this->removeMemoryProviderIfIsSingleConfigured();
|
||||
|
||||
$newData = $this->manipulator->getData();
|
||||
if ($newData['security'] && !\array_key_exists('providers', $newData['security'])) {
|
||||
$newData['security']['_providers'] = $this->manipulator->createEmptyLine();
|
||||
}
|
||||
|
||||
$newData['security']['providers']['__'] = $this->manipulator->createCommentLine(
|
||||
' used to reload user from session & other features (e.g. switch_user)'
|
||||
);
|
||||
if ($userConfig->isEntity()) {
|
||||
$newData['security']['providers']['app_user_provider'] = [
|
||||
'entity' => [
|
||||
'class' => $userClass,
|
||||
'property' => $userConfig->getIdentityPropertyName(),
|
||||
],
|
||||
];
|
||||
} else {
|
||||
if (!$userConfig->getUserProviderClass()) {
|
||||
throw new \LogicException('User provider class must be set for non-entity user.');
|
||||
}
|
||||
|
||||
$newData['security']['providers']['app_user_provider'] = [
|
||||
'id' => $userConfig->getUserProviderClass(),
|
||||
];
|
||||
}
|
||||
$this->manipulator->setData($newData);
|
||||
}
|
||||
|
||||
private function updatePasswordHashers(string $userClass): void
|
||||
{
|
||||
$newData = $this->manipulator->getData();
|
||||
|
||||
if (isset($newData['security']['encoders'])) {
|
||||
throw new \RuntimeException('Password Encoders are no longer supported by MakerBundle. Please update your "config/packages/security.yaml" file to use Password Hashers instead.');
|
||||
}
|
||||
|
||||
// The security-bundle recipe sets the password hasher via Flex. If it exists, move on...
|
||||
if (isset($newData['security']['password_hashers'][PasswordAuthenticatedUserInterface::class])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// by convention, password_hashers are put before the user provider option
|
||||
$providersIndex = array_search('providers', array_keys($newData['security']));
|
||||
|
||||
if (false === $providersIndex) {
|
||||
$newData['security'] = ['password_hashers' => []] + $newData['security'];
|
||||
} else {
|
||||
$newData['security'] = array_merge(
|
||||
\array_slice($newData['security'], 0, $providersIndex),
|
||||
['password_hashers' => []],
|
||||
\array_slice($newData['security'], $providersIndex)
|
||||
);
|
||||
}
|
||||
|
||||
$newData['security']['password_hashers'][$userClass] = [
|
||||
'algorithm' => 'auto',
|
||||
];
|
||||
|
||||
$newData['security']['password_hashers']['_'] = $this->manipulator->createEmptyLine();
|
||||
|
||||
$this->manipulator->setData($newData);
|
||||
}
|
||||
|
||||
private function removeMemoryProviderIfIsSingleConfigured(): void
|
||||
{
|
||||
if (!$this->isSingleInMemoryProviderConfigured()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$newData = $this->manipulator->getData();
|
||||
|
||||
$memoryProviderName = array_keys($newData['security']['providers'])[0];
|
||||
|
||||
$newData['security']['providers'] = [];
|
||||
|
||||
foreach ($newData['security']['firewalls'] as &$firewall) {
|
||||
if (($firewall['provider'] ?? null) === $memoryProviderName) {
|
||||
$firewall['provider'] = 'app_user_provider';
|
||||
}
|
||||
}
|
||||
|
||||
$this->manipulator->setData($newData);
|
||||
}
|
||||
|
||||
private function isSingleInMemoryProviderConfigured(): bool
|
||||
{
|
||||
if (!isset($this->manipulator->getData()['security']['providers'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$providersConfig = $this->manipulator->getData()['security']['providers'];
|
||||
|
||||
if (1 !== \count($providersConfig)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$firstProviderConfig = array_values($providersConfig)[0];
|
||||
|
||||
return \array_key_exists('memory', $firstProviderConfig);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony MakerBundle package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\MakerBundle\Security;
|
||||
|
||||
use PhpParser\Builder\Param;
|
||||
use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class SecurityControllerBuilder
|
||||
{
|
||||
public function addLoginMethod(ClassSourceManipulator $manipulator): void
|
||||
{
|
||||
$loginMethodBuilder = $manipulator->createMethodBuilder('login', 'Response', false);
|
||||
|
||||
$loginMethodBuilder->addAttribute($manipulator->buildAttributeNode(Route::class, ['path' => '/login', 'name' => 'app_login']));
|
||||
|
||||
$manipulator->addUseStatementIfNecessary(Response::class);
|
||||
$manipulator->addUseStatementIfNecessary(Route::class);
|
||||
$manipulator->addUseStatementIfNecessary(AuthenticationUtils::class);
|
||||
|
||||
$loginMethodBuilder->addParam(
|
||||
(new Param('authenticationUtils'))->setType('AuthenticationUtils')
|
||||
);
|
||||
|
||||
$manipulator->addMethodBody($loginMethodBuilder, <<<'CODE'
|
||||
<?php
|
||||
// if ($this->getUser()) {
|
||||
// return $this->redirectToRoute('target_path');
|
||||
// }
|
||||
CODE
|
||||
);
|
||||
$loginMethodBuilder->addStmt($manipulator->createMethodLevelBlankLine());
|
||||
$manipulator->addMethodBody($loginMethodBuilder, <<<'CODE'
|
||||
<?php
|
||||
// get the login error if there is one
|
||||
$error = $authenticationUtils->getLastAuthenticationError();
|
||||
// last username entered by the user
|
||||
$lastUsername = $authenticationUtils->getLastUsername();
|
||||
CODE
|
||||
);
|
||||
$loginMethodBuilder->addStmt($manipulator->createMethodLevelBlankLine());
|
||||
$manipulator->addMethodBody($loginMethodBuilder, <<<'CODE'
|
||||
<?php
|
||||
return $this->render(
|
||||
'security/login.html.twig',
|
||||
[
|
||||
'last_username' => $lastUsername,
|
||||
'error' => $error,
|
||||
]
|
||||
);
|
||||
CODE
|
||||
);
|
||||
$manipulator->addMethodBuilder($loginMethodBuilder);
|
||||
}
|
||||
|
||||
public function addLogoutMethod(ClassSourceManipulator $manipulator): void
|
||||
{
|
||||
$logoutMethodBuilder = $manipulator->createMethodBuilder('logout', 'void', false);
|
||||
|
||||
$logoutMethodBuilder->addAttribute($manipulator->buildAttributeNode(Route::class, ['path' => '/logout', 'name' => 'app_logout']));
|
||||
|
||||
$manipulator->addUseStatementIfNecessary(Route::class);
|
||||
$manipulator->addMethodBody($logoutMethodBuilder, <<<'CODE'
|
||||
<?php
|
||||
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
|
||||
CODE
|
||||
);
|
||||
$manipulator->addMethodBuilder($logoutMethodBuilder);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony MakerBundle package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\MakerBundle\Security;
|
||||
|
||||
use PhpParser\Node;
|
||||
use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator;
|
||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* Adds logic to implement UserInterface to an existing User class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class UserClassBuilder
|
||||
{
|
||||
public function addUserInterfaceImplementation(ClassSourceManipulator $manipulator, UserClassConfiguration $userClassConfig): void
|
||||
{
|
||||
$manipulator->addInterface(UserInterface::class);
|
||||
|
||||
$this->addGetUsername($manipulator, $userClassConfig);
|
||||
|
||||
$this->addGetRoles($manipulator, $userClassConfig);
|
||||
|
||||
$this->addPasswordImplementation($manipulator, $userClassConfig);
|
||||
|
||||
$this->addEraseCredentials($manipulator);
|
||||
}
|
||||
|
||||
private function addPasswordImplementation(ClassSourceManipulator $manipulator, UserClassConfiguration $userClassConfig): void
|
||||
{
|
||||
if (!$userClassConfig->hasPassword()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$manipulator->addInterface(PasswordAuthenticatedUserInterface::class);
|
||||
|
||||
$this->addGetPassword($manipulator, $userClassConfig);
|
||||
}
|
||||
|
||||
private function addGetUsername(ClassSourceManipulator $manipulator, UserClassConfiguration $userClassConfig): void
|
||||
{
|
||||
if ($userClassConfig->isEntity()) {
|
||||
// add entity property
|
||||
$manipulator->addEntityField(
|
||||
$userClassConfig->getIdentityPropertyName(),
|
||||
[
|
||||
'type' => 'string',
|
||||
// https://github.com/FriendsOfSymfony/FOSUserBundle/issues/1919
|
||||
'length' => 180,
|
||||
'unique' => true,
|
||||
]
|
||||
);
|
||||
} else {
|
||||
// add normal property
|
||||
$manipulator->addProperty(
|
||||
name: $userClassConfig->getIdentityPropertyName()
|
||||
);
|
||||
|
||||
$manipulator->addGetter(
|
||||
$userClassConfig->getIdentityPropertyName(),
|
||||
'string',
|
||||
true
|
||||
);
|
||||
|
||||
$manipulator->addSetter(
|
||||
$userClassConfig->getIdentityPropertyName(),
|
||||
'string',
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
// define getUsername (if it was defined above, this will override)
|
||||
$manipulator->addAccessorMethod(
|
||||
$userClassConfig->getIdentityPropertyName(),
|
||||
'getUserIdentifier',
|
||||
'string',
|
||||
false,
|
||||
[
|
||||
'A visual identifier that represents this user.',
|
||||
'',
|
||||
'@see UserInterface',
|
||||
],
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
private function addGetRoles(ClassSourceManipulator $manipulator, UserClassConfiguration $userClassConfig): void
|
||||
{
|
||||
if ($userClassConfig->isEntity()) {
|
||||
// add entity property
|
||||
$manipulator->addEntityField(
|
||||
'roles',
|
||||
[
|
||||
'type' => 'json',
|
||||
]
|
||||
);
|
||||
} else {
|
||||
// add normal property
|
||||
$manipulator->addProperty(
|
||||
name: 'roles',
|
||||
defaultValue: new Node\Expr\Array_([], ['kind' => Node\Expr\Array_::KIND_SHORT])
|
||||
);
|
||||
|
||||
$manipulator->addGetter(
|
||||
'roles',
|
||||
'array',
|
||||
false
|
||||
);
|
||||
|
||||
$manipulator->addSetter(
|
||||
'roles',
|
||||
'array',
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
// define getRoles (if it was defined above, this will override)
|
||||
$builder = $manipulator->createMethodBuilder(
|
||||
'getRoles',
|
||||
'array',
|
||||
false,
|
||||
['@see UserInterface']
|
||||
);
|
||||
|
||||
// $roles = $this->roles
|
||||
$builder->addStmt(
|
||||
new Node\Stmt\Expression(new Node\Expr\Assign(
|
||||
new Node\Expr\Variable('roles'),
|
||||
new Node\Expr\PropertyFetch(new Node\Expr\Variable('this'), 'roles')
|
||||
))
|
||||
);
|
||||
// comment line
|
||||
$builder->addStmt(
|
||||
$manipulator->createMethodLevelCommentNode(
|
||||
'guarantee every user at least has ROLE_USER'
|
||||
)
|
||||
);
|
||||
// $roles[] = 'ROLE_USER';
|
||||
$builder->addStmt(
|
||||
new Node\Stmt\Expression(
|
||||
new Node\Expr\Assign(
|
||||
new Node\Expr\ArrayDimFetch(
|
||||
new Node\Expr\Variable('roles')
|
||||
),
|
||||
new Node\Scalar\String_('ROLE_USER')
|
||||
)
|
||||
)
|
||||
);
|
||||
$builder->addStmt($manipulator->createMethodLevelBlankLine());
|
||||
// return array_unique($roles);
|
||||
$builder->addStmt(
|
||||
new Node\Stmt\Return_(
|
||||
new Node\Expr\FuncCall(
|
||||
new Node\Name('array_unique'),
|
||||
[new Node\Expr\Variable('roles')]
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$manipulator->addMethodBuilder($builder);
|
||||
}
|
||||
|
||||
private function addGetPassword(ClassSourceManipulator $manipulator, UserClassConfiguration $userClassConfig): void
|
||||
{
|
||||
if (!$userClassConfig->hasPassword()) {
|
||||
// add an empty method only
|
||||
$builder = $manipulator->createMethodBuilder(
|
||||
'getPassword',
|
||||
'string',
|
||||
true,
|
||||
[
|
||||
'This method can be removed in Symfony 6.0 - is not needed for apps that do not check user passwords.',
|
||||
'',
|
||||
'@see PasswordAuthenticatedUserInterface',
|
||||
]
|
||||
);
|
||||
|
||||
$builder->addStmt(
|
||||
new Node\Stmt\Return_(
|
||||
new Node\Expr\ConstFetch(
|
||||
new Node\Name('null')
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$manipulator->addMethodBuilder($builder);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$propertyDocs = '@var string The hashed password';
|
||||
if ($userClassConfig->isEntity()) {
|
||||
// add entity property
|
||||
$manipulator->addEntityField(
|
||||
'password',
|
||||
[
|
||||
'type' => 'string',
|
||||
],
|
||||
[$propertyDocs]
|
||||
);
|
||||
} else {
|
||||
// add normal property
|
||||
$manipulator->addProperty(
|
||||
name: 'password',
|
||||
comments: [$propertyDocs]
|
||||
);
|
||||
|
||||
$manipulator->addGetter(
|
||||
'password',
|
||||
'string',
|
||||
true
|
||||
);
|
||||
|
||||
$manipulator->addSetter(
|
||||
'password',
|
||||
'string',
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
// define getPassword (if it was defined above, this will override)
|
||||
$manipulator->addAccessorMethod(
|
||||
'password',
|
||||
'getPassword',
|
||||
'string',
|
||||
false,
|
||||
[
|
||||
'@see PasswordAuthenticatedUserInterface',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function addEraseCredentials(ClassSourceManipulator $manipulator): void
|
||||
{
|
||||
// add eraseCredentials: always empty
|
||||
$builder = $manipulator->createMethodBuilder(
|
||||
'eraseCredentials',
|
||||
'void',
|
||||
false,
|
||||
['@see UserInterface']
|
||||
);
|
||||
$builder->addStmt(
|
||||
$manipulator->createMethodLevelCommentNode(
|
||||
'If you store any temporary, sensitive data on the user, clear it here'
|
||||
)
|
||||
);
|
||||
$builder->addStmt(
|
||||
$manipulator->createMethodLevelCommentNode(
|
||||
'$this->plainPassword = null;'
|
||||
)
|
||||
);
|
||||
|
||||
$manipulator->addMethodBuilder($builder);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony MakerBundle package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\MakerBundle\Security;
|
||||
|
||||
/**
|
||||
* Configuration about the user's new User class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class UserClassConfiguration
|
||||
{
|
||||
private string $userProviderClass;
|
||||
|
||||
public function __construct(
|
||||
private bool $isEntity,
|
||||
private string $identityPropertyName,
|
||||
private bool $hasPassword,
|
||||
) {
|
||||
}
|
||||
|
||||
public function isEntity(): bool
|
||||
{
|
||||
return $this->isEntity;
|
||||
}
|
||||
|
||||
public function getIdentityPropertyName(): string
|
||||
{
|
||||
return $this->identityPropertyName;
|
||||
}
|
||||
|
||||
public function hasPassword(): bool
|
||||
{
|
||||
return $this->hasPassword;
|
||||
}
|
||||
|
||||
public function getUserProviderClass(): string
|
||||
{
|
||||
return $this->userProviderClass;
|
||||
}
|
||||
|
||||
public function setUserProviderClass(string $userProviderClass): void
|
||||
{
|
||||
if ($this->isEntity()) {
|
||||
throw new \LogicException('No custom user class allowed for entity user.');
|
||||
}
|
||||
|
||||
$this->userProviderClass = $userProviderClass;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user