diff --git a/Makefile b/Makefile index 49d6c3ae25..8369896619 100644 --- a/Makefile +++ b/Makefile @@ -879,6 +879,7 @@ TEST_WEB_82 := \ test_web_slim_4 \ test_web_symfony_52 \ test_web_symfony_62 \ + test_web_symfony_70 \ test_web_wordpress_59 \ test_web_wordpress_61 \ test_web_custom \ @@ -925,6 +926,7 @@ TEST_WEB_83 := \ test_web_slim_4 \ test_web_symfony_52 \ test_web_symfony_62 \ + test_web_symfony_70 \ test_web_wordpress_59 \ test_web_wordpress_61 \ test_web_custom \ @@ -1213,6 +1215,10 @@ test_web_symfony_62: global_test_run_dependencies $(COMPOSER) --working-dir=tests/Frameworks/Symfony/Version_6_2 update php tests/Frameworks/Symfony/Version_6_2/bin/console cache:clear --no-warmup --env=prod $(call run_tests,--testsuite=symfony-62-test) +test_web_symfony_70: global_test_run_dependencies + $(COMPOSER) --working-dir=tests/Frameworks/Symfony/Version_7_0 update + php tests/Frameworks/Symfony/Version_7_0/bin/console cache:clear --no-warmup --env=prod + $(call run_tests,--testsuite=symfony-70-test) test_web_wordpress_48: global_test_run_dependencies $(call run_tests,tests/Integrations/WordPress/V4_8) diff --git a/tests/Frameworks/Symfony/Version_7_0/.env b/tests/Frameworks/Symfony/Version_7_0/.env new file mode 100644 index 0000000000..4dbed61591 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/.env @@ -0,0 +1,30 @@ +# In all environments, the following files are loaded if they exist, +# the latter taking precedence over the former: +# +# * .env contains default values for the environment variables needed by the app +# * .env.local uncommitted file with local overrides +# * .env.$APP_ENV committed environment-specific defaults +# * .env.$APP_ENV.local uncommitted environment-specific overrides +# +# Real environment variables win over .env files. +# +# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. +# https://symfony.com/doc/current/configuration/secrets.html +# +# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). +# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration + +###> symfony/framework-bundle ### +APP_ENV=dev +APP_SECRET=7b46ee8a78f39224283035fe148d0a79 +###< symfony/framework-bundle ### + +###> doctrine/doctrine-bundle ### +# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url +# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml +# +# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" +# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4" +# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4" +DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=15&charset=utf8" +###< doctrine/doctrine-bundle ### diff --git a/tests/Frameworks/Symfony/Version_7_0/.gitignore b/tests/Frameworks/Symfony/Version_7_0/.gitignore new file mode 100644 index 0000000000..a67f91e25c --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/.gitignore @@ -0,0 +1,10 @@ + +###> symfony/framework-bundle ### +/.env.local +/.env.local.php +/.env.*.local +/config/secrets/prod/prod.decrypt.private.php +/public/bundles/ +/var/ +/vendor/ +###< symfony/framework-bundle ### diff --git a/tests/Frameworks/Symfony/Version_7_0/bin/console b/tests/Frameworks/Symfony/Version_7_0/bin/console new file mode 100755 index 0000000000..c933dc535d --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/bin/console @@ -0,0 +1,17 @@ +#!/usr/bin/env php + doctrine/doctrine-bundle ### + database: + ports: + - "5432" +###< doctrine/doctrine-bundle ### diff --git a/tests/Frameworks/Symfony/Version_7_0/compose.yaml b/tests/Frameworks/Symfony/Version_7_0/compose.yaml new file mode 100644 index 0000000000..1067b9ca01 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/compose.yaml @@ -0,0 +1,21 @@ +version: '3' + +services: +###> doctrine/doctrine-bundle ### + database: + image: postgres:${POSTGRES_VERSION:-15}-alpine + environment: + POSTGRES_DB: ${POSTGRES_DB:-app} + # You should definitely change the password in production + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!} + POSTGRES_USER: ${POSTGRES_USER:-app} + volumes: + - database_data:/var/lib/postgresql/data:rw + # You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data! + # - ./docker/db/data:/var/lib/postgresql/data:rw +###< doctrine/doctrine-bundle ### + +volumes: +###> doctrine/doctrine-bundle ### + database_data: +###< doctrine/doctrine-bundle ### diff --git a/tests/Frameworks/Symfony/Version_7_0/composer.json b/tests/Frameworks/Symfony/Version_7_0/composer.json new file mode 100644 index 0000000000..7e89f29fc8 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/composer.json @@ -0,0 +1,87 @@ +{ + "type": "project", + "license": "proprietary", + "minimum-stability": "stable", + "prefer-stable": true, + "require": { + "php": ">=8.2", + "ext-ctype": "*", + "ext-iconv": "*", + "doctrine/annotations": "^2.0", + "doctrine/doctrine-bundle": "^2.11", + "doctrine/doctrine-migrations-bundle": "^3.3", + "doctrine/orm": "^2.17", + "symfony/console": "7.0.*", + "symfony/dotenv": "7.0.*", + "symfony/flex": "^2", + "symfony/form": "7.0.*", + "symfony/framework-bundle": "7.0.*", + "symfony/monolog-bundle": "^3.10", + "symfony/runtime": "7.0.*", + "symfony/security-bundle": "7.0.*", + "symfony/twig-bundle": "7.0.*", + "symfony/validator": "7.0.*", + "symfony/yaml": "7.0.*", + "symfonycasts/verify-email-bundle": "^1.16" + }, + "config": { + "allow-plugins": { + "symfony/flex": true, + "php-http/discovery": true, + "symfony/runtime": true + }, + "sort-packages": true + }, + "autoload": { + "psr-4": { + "App\\": "src/" + }, + "files": ["../../../Appsec/Mock.php"] + }, + "autoload-dev": { + "psr-4": { + "App\\Tests\\": "tests/" + } + }, + "replace": { + "symfony/polyfill-ctype": "*", + "symfony/polyfill-iconv": "*", + "symfony/polyfill-php72": "*", + "symfony/polyfill-php73": "*", + "symfony/polyfill-php74": "*", + "symfony/polyfill-php80": "*", + "symfony/polyfill-php81": "*", + "symfony/polyfill-php82": "*" + }, + "scripts": { + "auto-scripts": { + "cache:clear": "symfony-cmd", + "assets:install %PUBLIC_DIR%": "symfony-cmd" + }, + "post-install-cmd": [ + "@auto-scripts" + ], + "post-update-cmd": [ + "@auto-scripts" + ], + "post-autoload-dump": [ + "rm -rf var/cache/dev/*", + "rm -rf var/cache/prod/*", + "@php bin/console doctrine:database:drop --force", + "@php bin/console doctrine:database:create", + "@php bin/console doctrine:migrations:migrate -n" + ] + }, + "conflict": { + "symfony/symfony": "*" + }, + "extra": { + "symfony": { + "allow-contrib": false, + "require": "7.0.*" + } + }, + "require-dev": { + "symfony/maker-bundle": "^1.49" + } +} diff --git a/tests/Frameworks/Symfony/Version_7_0/config/bundles.php b/tests/Frameworks/Symfony/Version_7_0/config/bundles.php new file mode 100644 index 0000000000..1b41cf8c0b --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/config/bundles.php @@ -0,0 +1,12 @@ + ['all' => true], + Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], + Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], + Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], + Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], + Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], + SymfonyCasts\Bundle\VerifyEmail\SymfonyCastsVerifyEmailBundle::class => ['all' => true], + Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], +]; diff --git a/tests/Frameworks/Symfony/Version_7_0/config/packages/cache.yaml b/tests/Frameworks/Symfony/Version_7_0/config/packages/cache.yaml new file mode 100644 index 0000000000..6899b72003 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/config/packages/cache.yaml @@ -0,0 +1,19 @@ +framework: + cache: + # Unique name of your app: used to compute stable namespaces for cache keys. + #prefix_seed: your_vendor_name/app_name + + # The "app" cache stores to the filesystem by default. + # The data in this cache should persist between deploys. + # Other options include: + + # Redis + #app: cache.adapter.redis + #default_redis_provider: redis://localhost + + # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) + #app: cache.adapter.apcu + + # Namespaced pools use the above "app" backend by default + #pools: + #my.dedicated.cache: null diff --git a/tests/Frameworks/Symfony/Version_7_0/config/packages/doctrine.yaml b/tests/Frameworks/Symfony/Version_7_0/config/packages/doctrine.yaml new file mode 100644 index 0000000000..11d765735f --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/config/packages/doctrine.yaml @@ -0,0 +1,49 @@ +doctrine: + dbal: + url: 'mysql://test:test@mysql_integration:3306/test?serverVersion=5&charset=utf8mb4' + + # IMPORTANT: You MUST configure your server version, + # either here or in the DATABASE_URL env var (see .env file) + #server_version: '15' + + profiling_collect_backtrace: '%kernel.debug%' + orm: + auto_generate_proxy_classes: true + enable_lazy_ghost_objects: true + report_fields_where_declared: true + validate_xml_mapping: true + naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware + auto_mapping: true + mappings: + App: + type: attribute + is_bundle: false + dir: '%kernel.project_dir%/src/Entity' + prefix: 'App\Entity' + alias: App + +when@test: + doctrine: + dbal: + # "TEST_TOKEN" is typically set by ParaTest + dbname_suffix: '_test%env(default::TEST_TOKEN)%' + +when@prod: + doctrine: + orm: + auto_generate_proxy_classes: false + proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies' + query_cache_driver: + type: pool + pool: doctrine.system_cache_pool + result_cache_driver: + type: pool + pool: doctrine.result_cache_pool + + framework: + cache: + pools: + doctrine.result_cache_pool: + adapter: cache.app + doctrine.system_cache_pool: + adapter: cache.system diff --git a/tests/Frameworks/Symfony/Version_7_0/config/packages/doctrine_migrations.yaml b/tests/Frameworks/Symfony/Version_7_0/config/packages/doctrine_migrations.yaml new file mode 100644 index 0000000000..29231d94bd --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/config/packages/doctrine_migrations.yaml @@ -0,0 +1,6 @@ +doctrine_migrations: + migrations_paths: + # namespace is arbitrary but should be different from App\Migrations + # as migrations classes should NOT be autoloaded + 'DoctrineMigrations': '%kernel.project_dir%/migrations' + enable_profiler: false diff --git a/tests/Frameworks/Symfony/Version_7_0/config/packages/framework.yaml b/tests/Frameworks/Symfony/Version_7_0/config/packages/framework.yaml new file mode 100644 index 0000000000..949ea2f45f --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/config/packages/framework.yaml @@ -0,0 +1,23 @@ +# see https://symfony.com/doc/current/reference/configuration/framework.html +framework: + secret: '%env(APP_SECRET)%' + csrf_protection: false + handle_all_throwables: true + + # Enables session support. Note that the session will ONLY be started if you read or write from it. + # Remove or comment this section to explicitly disable session support. + session: + handler_id: null + cookie_secure: false + cookie_samesite: lax + + #esi: true + #fragments: true + php_errors: + log: true + +when@test: + framework: + test: true + session: + storage_factory_id: session.storage.factory.mock_file diff --git a/tests/Frameworks/Symfony/Version_7_0/config/packages/monolog.yaml b/tests/Frameworks/Symfony/Version_7_0/config/packages/monolog.yaml new file mode 100644 index 0000000000..8c9efa91e0 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/config/packages/monolog.yaml @@ -0,0 +1,61 @@ +monolog: + channels: + - deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists + +when@dev: + monolog: + handlers: + main: + type: stream + path: "%kernel.logs_dir%/%kernel.environment%.log" + level: debug + channels: ["!event"] + # uncomment to get logging in your browser + # you may have to allow bigger header sizes in your Web server configuration + #firephp: + # type: firephp + # level: info + #chromephp: + # type: chromephp + # level: info + console: + type: console + process_psr_3_messages: false + channels: ["!event", "!doctrine", "!console"] + +when@test: + monolog: + handlers: + main: + type: fingers_crossed + action_level: error + handler: nested + excluded_http_codes: [404, 405] + channels: ["!event"] + nested: + type: stream + path: "%kernel.logs_dir%/%kernel.environment%.log" + level: debug + +when@prod: + monolog: + handlers: + main: + type: fingers_crossed + action_level: error + handler: nested + excluded_http_codes: [404, 405] + buffer_size: 50 # How many messages should be saved? Prevent memory leaks + nested: + type: stream + path: php://stderr + level: debug + formatter: monolog.formatter.json + console: + type: console + process_psr_3_messages: false + channels: ["!event", "!doctrine"] + deprecation: + type: stream + channels: [deprecation] + path: php://stderr diff --git a/tests/Frameworks/Symfony/Version_7_0/config/packages/routing.yaml b/tests/Frameworks/Symfony/Version_7_0/config/packages/routing.yaml new file mode 100644 index 0000000000..4b766ce57f --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/config/packages/routing.yaml @@ -0,0 +1,12 @@ +framework: + router: + utf8: true + + # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. + # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands + #default_uri: http://localhost + +when@prod: + framework: + router: + strict_requirements: null diff --git a/tests/Frameworks/Symfony/Version_7_0/config/packages/security.yaml b/tests/Frameworks/Symfony/Version_7_0/config/packages/security.yaml new file mode 100644 index 0000000000..a370a958a3 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/config/packages/security.yaml @@ -0,0 +1,46 @@ +security: + # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords + password_hashers: + Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' + # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider + providers: + # used to reload user from session & other features (e.g. switch_user) + app_user_provider: + entity: + class: App\Entity\User + property: email + firewalls: + dev: + pattern: ^/(_(profiler|wdt)|css|images|js)/ + security: false + main: + lazy: true + provider: app_user_provider + + # activate different ways to authenticate + # https://symfony.com/doc/current/security.html#the-firewall + + # https://symfony.com/doc/current/security/impersonating_user.html + # switch_user: true + form_login: + # "app_login" is the name of the route created previously + login_path: app_login + check_path: app_login + # Easy way to control access for large sections of your site + # Note: Only the *first* access control that matches will be used + access_control: + # - { path: ^/admin, roles: ROLE_ADMIN } + # - { path: ^/profile, roles: ROLE_USER } + +when@test: + security: + password_hashers: + # By default, password hashers are resource intensive and take time. This is + # important to generate secure password hashes. In tests however, secure hashes + # are not important, waste resources and increase test times. The following + # reduces the work factor to the lowest possible values. + Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: + algorithm: auto + cost: 4 # Lowest possible value for bcrypt + time_cost: 3 # Lowest possible value for argon + memory_cost: 10 # Lowest possible value for argon diff --git a/tests/Frameworks/Symfony/Version_7_0/config/packages/test/framework.yaml b/tests/Frameworks/Symfony/Version_7_0/config/packages/test/framework.yaml new file mode 100644 index 0000000000..d051c84008 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/config/packages/test/framework.yaml @@ -0,0 +1,4 @@ +framework: + test: true + session: + storage_id: session.storage.mock_file diff --git a/tests/Frameworks/Symfony/Version_7_0/config/packages/test/twig.yaml b/tests/Frameworks/Symfony/Version_7_0/config/packages/test/twig.yaml new file mode 100644 index 0000000000..8c6e0b401d --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/config/packages/test/twig.yaml @@ -0,0 +1,2 @@ +twig: + strict_variables: true diff --git a/tests/Frameworks/Symfony/Version_7_0/config/packages/twig.yaml b/tests/Frameworks/Symfony/Version_7_0/config/packages/twig.yaml new file mode 100644 index 0000000000..f9f4cc539b --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/config/packages/twig.yaml @@ -0,0 +1,6 @@ +twig: + default_path: '%kernel.project_dir%/templates' + +when@test: + twig: + strict_variables: true diff --git a/tests/Frameworks/Symfony/Version_7_0/config/packages/validator.yaml b/tests/Frameworks/Symfony/Version_7_0/config/packages/validator.yaml new file mode 100644 index 0000000000..0201281d3c --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/config/packages/validator.yaml @@ -0,0 +1,13 @@ +framework: + validation: + email_validation_mode: html5 + + # Enables validator auto-mapping support. + # For instance, basic validation constraints will be inferred from Doctrine's metadata. + #auto_mapping: + # App\Entity\: [] + +when@test: + framework: + validation: + not_compromised_password: false diff --git a/tests/Frameworks/Symfony/Version_7_0/config/preload.php b/tests/Frameworks/Symfony/Version_7_0/config/preload.php new file mode 100644 index 0000000000..5ebcdb2153 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/config/preload.php @@ -0,0 +1,5 @@ +addSql('CREATE TABLE `user` (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(180) NOT NULL, roles LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', password VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_8D93D649E7927C74 (email), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP TABLE `user`'); + } +} diff --git a/tests/Frameworks/Symfony/Version_7_0/migrations/Version20231227143831.php b/tests/Frameworks/Symfony/Version_7_0/migrations/Version20231227143831.php new file mode 100644 index 0000000000..7f6d49843f --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/migrations/Version20231227143831.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE user ADD is_verified TINYINT(1) NOT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE `user` DROP is_verified'); + } +} diff --git a/tests/Frameworks/Symfony/Version_7_0/public/index.php b/tests/Frameworks/Symfony/Version_7_0/public/index.php new file mode 100644 index 0000000000..9982c218d6 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/public/index.php @@ -0,0 +1,9 @@ +render('twig_template.html.twig', [ + 'base_dir' => realpath($this->getParameter('kernel.project_dir')).DIRECTORY_SEPARATOR, + ]); + } + + #[Route('/error', name: 'error')] + public function errorAction(Request $request) + { + throw new \Exception('An exception occurred'); + } +} diff --git a/tests/Frameworks/Symfony/Version_7_0/src/Controller/LoginController.php b/tests/Frameworks/Symfony/Version_7_0/src/Controller/LoginController.php new file mode 100644 index 0000000000..4cfa2d14fb --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/src/Controller/LoginController.php @@ -0,0 +1,27 @@ +getLastAuthenticationError(); + + // last username entered by the user + $lastUsername = $authenticationUtils->getLastUsername(); + + return $this->render('login/index.html.twig', [ + 'last_username' => $lastUsername, + 'error' => $error, + ]); + } +} diff --git a/tests/Frameworks/Symfony/Version_7_0/src/Controller/RegistrationController.php b/tests/Frameworks/Symfony/Version_7_0/src/Controller/RegistrationController.php new file mode 100644 index 0000000000..c0ea3b78ca --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/src/Controller/RegistrationController.php @@ -0,0 +1,65 @@ +logger = $logger; + } + + #[Route('/register', name: 'app_register')] + public function register(Request $request, UserPasswordHasherInterface $userPasswordHasher, EntityManagerInterface $entityManager): Response + { + $this->logger->debug('Registering a new user'); + $user = new User(); + $this->logger->debug('Created a new user'); + $form = $this->createForm(RegistrationFormType::class, $user); + $this->logger->debug('Created a new form'); + $form->handleRequest($request); + $this->logger->debug('Handled a new form'); + + $this->logger->debug('Form is submitted: ' . $form->isSubmitted()); + $this->logger->debug('Form is valid: ' . $form->isValid()); + // Print errors + foreach ($form->getErrors(true) as $error) { + $this->logger->debug('Error: ' . $error->getMessage()); + } + + if ($form->isSubmitted() && $form->isValid()) { + $this->logger->debug('Form is valid'); + // encode the plain password + $user->setPassword( + $userPasswordHasher->hashPassword( + $user, + $form->get('plainPassword')->getData() + ) + ); + + $entityManager->persist($user); + $entityManager->flush(); + // do anything else you need here, like send an email + + return $this->redirectToRoute('simple'); + } + $this->logger->debug('Form is not valid'); + + return $this->render('registration/register.html.twig', [ + 'registrationForm' => $form->createView(), + ]); + } +} diff --git a/tests/Frameworks/Symfony/Version_7_0/src/Entity/.gitignore b/tests/Frameworks/Symfony/Version_7_0/src/Entity/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/Frameworks/Symfony/Version_7_0/src/Entity/User.php b/tests/Frameworks/Symfony/Version_7_0/src/Entity/User.php new file mode 100644 index 0000000000..c80827b39c --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/src/Entity/User.php @@ -0,0 +1,117 @@ +id; + } + + public function getEmail(): ?string + { + return $this->email; + } + + public function setEmail(string $email): static + { + $this->email = $email; + + return $this; + } + + /** + * A visual identifier that represents this user. + * + * @see UserInterface + */ + public function getUserIdentifier(): string + { + return (string) $this->email; + } + + /** + * @see UserInterface + */ + public function getRoles(): array + { + $roles = $this->roles; + // guarantee every user at least has ROLE_USER + $roles[] = 'ROLE_USER'; + + return array_unique($roles); + } + + public function setRoles(array $roles): static + { + $this->roles = $roles; + + return $this; + } + + /** + * @see PasswordAuthenticatedUserInterface + */ + public function getPassword(): string + { + return $this->password; + } + + public function setPassword(string $password): static + { + $this->password = $password; + + return $this; + } + + /** + * @see UserInterface + */ + public function eraseCredentials(): void + { + // If you store any temporary, sensitive data on the user, clear it here + // $this->plainPassword = null; + } + + public function isVerified(): bool + { + return $this->isVerified; + } + + public function setIsVerified(bool $isVerified): static + { + $this->isVerified = $isVerified; + + return $this; + } +} diff --git a/tests/Frameworks/Symfony/Version_7_0/src/Form/RegistrationFormType.php b/tests/Frameworks/Symfony/Version_7_0/src/Form/RegistrationFormType.php new file mode 100644 index 0000000000..bbd42aec26 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/src/Form/RegistrationFormType.php @@ -0,0 +1,55 @@ +add('email') + ->add('agreeTerms', CheckboxType::class, [ + 'mapped' => false, + 'constraints' => [ + new IsTrue([ + 'message' => 'You should agree to our terms.', + ]), + ], + ]) + ->add('plainPassword', PasswordType::class, [ + // instead of being set onto the object directly, + // this is read and encoded in the controller + 'mapped' => false, + 'attr' => ['autocomplete' => 'new-password'], + 'constraints' => [ + new NotBlank([ + 'message' => 'Please enter a password', + ]), + new Length([ + 'min' => 6, + 'minMessage' => 'Your password should be at least {{ limit }} characters', + // max length allowed by Symfony for security reasons + 'max' => 4096, + ]), + ], + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => User::class, + 'csrf_protection' => false + ]); + } +} diff --git a/tests/Frameworks/Symfony/Version_7_0/src/Kernel.php b/tests/Frameworks/Symfony/Version_7_0/src/Kernel.php new file mode 100644 index 0000000000..655e796658 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/src/Kernel.php @@ -0,0 +1,38 @@ +import('../config/{packages}/*.yaml'); + $container->import('../config/{packages}/'.$this->environment.'/*.yaml'); + + if (is_file(\dirname(__DIR__).'/config/services.yaml')) { + $container->import('../config/services.yaml'); + $container->import('../config/{services}_'.$this->environment.'.yaml'); + } elseif (is_file($path = \dirname(__DIR__).'/config/services.php')) { + (require $path)($container->withPath($path), $this); + } + } + + protected function configureRoutes(RoutingConfigurator $routes): void + { + $routes->import('../config/{routes}/'.$this->environment.'/*.yaml'); + $routes->import('../config/{routes}/*.yaml'); + + if (is_file(\dirname(__DIR__).'/config/routes.yaml')) { + $routes->import('../config/routes.yaml'); + } elseif (is_file($path = \dirname(__DIR__).'/config/routes.php')) { + (require $path)($routes->withPath($path), $this); + } + } +} diff --git a/tests/Frameworks/Symfony/Version_7_0/src/Repository/.gitignore b/tests/Frameworks/Symfony/Version_7_0/src/Repository/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/Frameworks/Symfony/Version_7_0/src/Repository/UserRepository.php b/tests/Frameworks/Symfony/Version_7_0/src/Repository/UserRepository.php new file mode 100644 index 0000000000..c788f463d3 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/src/Repository/UserRepository.php @@ -0,0 +1,67 @@ + + * + * @implements PasswordUpgraderInterface + * + * @method User|null find($id, $lockMode = null, $lockVersion = null) + * @method User|null findOneBy(array $criteria, array $orderBy = null) + * @method User[] findAll() + * @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, User::class); + } + + /** + * Used to upgrade (rehash) the user's password automatically over time. + */ + public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void + { + if (!$user instanceof User) { + throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class)); + } + + $user->setPassword($newHashedPassword); + $this->getEntityManager()->persist($user); + $this->getEntityManager()->flush(); + } + +// /** +// * @return User[] Returns an array of User objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('u') +// ->andWhere('u.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('u.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?User +// { +// return $this->createQueryBuilder('u') +// ->andWhere('u.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/tests/Frameworks/Symfony/Version_7_0/templates/base.html.twig b/tests/Frameworks/Symfony/Version_7_0/templates/base.html.twig new file mode 100644 index 0000000000..16d7273bb4 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/templates/base.html.twig @@ -0,0 +1,19 @@ + + + + + {% block title %}Welcome!{% endblock %} + {# Run `composer require symfony/webpack-encore-bundle` + and uncomment the following Encore helpers to start using Symfony UX #} + {% block stylesheets %} + {#{{ encore_entry_link_tags('app') }}#} + {% endblock %} + + {% block javascripts %} + {#{{ encore_entry_script_tags('app') }}#} + {% endblock %} + + + {% block body %}{% endblock %} + + diff --git a/tests/Frameworks/Symfony/Version_7_0/templates/login/index.html.twig b/tests/Frameworks/Symfony/Version_7_0/templates/login/index.html.twig new file mode 100644 index 0000000000..fee5a5c26e --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/templates/login/index.html.twig @@ -0,0 +1,23 @@ +{# templates/login/index.html.twig #} +{% extends 'base.html.twig' %} + +{% block body %} + {% if error %} +
{{ error.messageKey|trans(error.messageData, 'security') }}
+ {% endif %} + +
+ + + + + + + + + {# If you want to control the URL the user is redirected to on success + #} + + +
+{% endblock %} diff --git a/tests/Frameworks/Symfony/Version_7_0/templates/registration/register.html.twig b/tests/Frameworks/Symfony/Version_7_0/templates/registration/register.html.twig new file mode 100644 index 0000000000..97f32c9af1 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/templates/registration/register.html.twig @@ -0,0 +1,19 @@ +{% extends 'base.html.twig' %} + +{% block title %}Register{% endblock %} + +{% block body %} +

Register

+ + {{ form_errors(registrationForm) }} + + {{ form_start(registrationForm) }} + {{ form_row(registrationForm.email) }} + {{ form_row(registrationForm.plainPassword, { + label: 'Password' + }) }} + {{ form_row(registrationForm.agreeTerms) }} + + + {{ form_end(registrationForm) }} +{% endblock %} diff --git a/tests/Frameworks/Symfony/Version_7_0/templates/twig_template.html.twig b/tests/Frameworks/Symfony/Version_7_0/templates/twig_template.html.twig new file mode 100644 index 0000000000..cacda46b55 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/templates/twig_template.html.twig @@ -0,0 +1 @@ +This is the view diff --git a/tests/Integrations/CLI/Symfony/V7_0/CommonScenariosTest.php b/tests/Integrations/CLI/Symfony/V7_0/CommonScenariosTest.php new file mode 100644 index 0000000000..992271a8bb --- /dev/null +++ b/tests/Integrations/CLI/Symfony/V7_0/CommonScenariosTest.php @@ -0,0 +1,11 @@ +connection()->exec('insert into user (roles, email, password) VALUES ("{}", "'.$email.'", "$2y$13$WNnAxSuifzgXGx9kYfFr.eMaXzE50MmrMnXxmrlZqxSa21oiMyy0i")'); + $this->connection()->exec('insert into user (roles, email, password) VALUES ("{}", "' . $email . '", "$2y$13$WNnAxSuifzgXGx9kYfFr.eMaXzE50MmrMnXxmrlZqxSa21oiMyy0i")'); $spec = PostSpec::create('request', '/login', [ - 'Content-Type: application/x-www-form-urlencoded' - ], "_username=$email&_password=$password"); + 'Content-Type: application/x-www-form-urlencoded' + ], "_username=$email&_password=$password"); - $this->call($spec, [ CURLOPT_FOLLOWLOCATION => false ]); + $this->call($spec, [CURLOPT_FOLLOWLOCATION => false]); $events = AppsecStatus::getInstance()->getEvents(); $this->assertEquals(1, count($events)); @@ -63,36 +63,35 @@ public function testUserLoginFailureEvent() $email = 'non-existing@email.com'; $password = 'some password'; $spec = PostSpec::create('request', '/login', [ - 'Content-Type: application/x-www-form-urlencoded' - ], "_username=$email&_password=$password"); + 'Content-Type: application/x-www-form-urlencoded' + ], "_username=$email&_password=$password"); - $this->call($spec, [ CURLOPT_FOLLOWLOCATION => false ]); + $this->call($spec, [CURLOPT_FOLLOWLOCATION => false]); - $events = AppsecStatus::getInstance()->getEvents(); - $this->assertEquals(1, count($events)); - $this->assertEmpty($events[0]['userId']); - $this->assertEmpty($events[0]['metadata']); - $this->assertTrue($events[0]['automated']); - $this->assertEquals('track_user_login_failure_event', $events[0]['eventName']); + $events = AppsecStatus::getInstance()->getEvents(); + $this->assertEquals(1, count($events)); + $this->assertEmpty($events[0]['userId']); + $this->assertEmpty($events[0]['metadata']); + $this->assertTrue($events[0]['automated']); + $this->assertEquals('track_user_login_failure_event', $events[0]['eventName']); } public function testUserSignUp() { - $email = 'test-user@email.com'; - $password = 'some password'; - $spec = PostSpec::create('Signup', '/register', [ - 'Content-Type: application/x-www-form-urlencoded' - ], "registration_form[email]=$email®istration_form[plainPassword]=$password®istration_form[agreeTerms]=1"); + $email = 'test-user@email.com'; + $password = 'some password'; + $spec = PostSpec::create('Signup', '/register', [ + 'Content-Type: application/x-www-form-urlencoded' + ], "registration_form[email]=$email®istration_form[plainPassword]=$password®istration_form[agreeTerms]=1"); - $this->call($spec, [ CURLOPT_FOLLOWLOCATION => false ]); + $this->call($spec, [CURLOPT_FOLLOWLOCATION => false]); - $users = $this->connection()->query("SELECT * FROM user where email='".$email."'")->fetchAll(); + $users = $this->connection()->query("SELECT * FROM user where email='" . $email . "'")->fetchAll(); $this->assertEquals(1, count($users)); $signUpEvent = null; - foreach(AppsecStatus::getInstance()->getEvents() as $event) - { + foreach (AppsecStatus::getInstance()->getEvents() as $event) { if ($event['eventName'] == 'track_user_signup_event') { $signUpEvent = $event; } diff --git a/tests/Integrations/Symfony/V7_0/AutomatedLoginEventsTest.php b/tests/Integrations/Symfony/V7_0/AutomatedLoginEventsTest.php new file mode 100644 index 0000000000..e2f4a56951 --- /dev/null +++ b/tests/Integrations/Symfony/V7_0/AutomatedLoginEventsTest.php @@ -0,0 +1,11 @@ + 'true', + 'DD_SERVICE' => 'test_symfony_70', + ]); + } + + /** + * @dataProvider provideSpecs + * @param RequestSpec $spec + * @param array $spanExpectations + * @throws \Exception + */ + public function testScenario(RequestSpec $spec, array $spanExpectations) + { + $traces = $this->tracesFromWebRequest(function () use ($spec) { + $this->call($spec); + }); + + $this->assertFlameGraph($traces, $spanExpectations); + } + + public function provideSpecs() + { + return $this->buildDataProvider( + [ + 'A simple GET request returning a string' => [ + SpanAssertion::build( + 'symfony.request', + 'test_symfony_70', + 'web', + 'simple' + )->withExactTags([ + 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleAction', + 'symfony.route.name' => 'simple', + 'http.method' => 'GET', + 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.status_code' => '200', + Tag::SPAN_KIND => 'server', + Tag::COMPONENT => 'symfony', + ])->withChildren([ + SpanAssertion::exists('symfony.httpkernel.kernel.handle') + ->withChildren([ + SpanAssertion::exists('symfony.httpkernel.kernel.boot'), + SpanAssertion::exists('symfony.kernel.handle') + ->withChildren([ + SpanAssertion::exists('symfony.kernel.request'), + SpanAssertion::exists('symfony.kernel.controller'), + SpanAssertion::exists('symfony.kernel.controller_arguments'), + SpanAssertion::exists('symfony.kernel.response'), + SpanAssertion::exists('symfony.kernel.finish_request'), + SpanAssertion::build( + 'symfony.controller', + 'test_symfony_70', + 'web', + 'App\Controller\CommonScenariosController::simpleAction' + )->withExactTags([ + Tag::COMPONENT => 'symfony', + ]) + ]), + ]), + SpanAssertion::exists('symfony.kernel.terminate'), + ]), + ], + 'A simple GET request with a view' => [ + SpanAssertion::build( + 'symfony.request', + 'test_symfony_70', + 'web', + 'simple_view' + )->withExactTags([ + 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleViewAction', + 'symfony.route.name' => 'simple_view', + 'http.method' => 'GET', + 'http.url' => 'http://localhost:9999/simple_view?key=value&', + 'http.status_code' => '200', + Tag::SPAN_KIND => 'server', + Tag::COMPONENT => 'symfony', + ])->withChildren([ + SpanAssertion::exists('symfony.kernel.terminate'), + SpanAssertion::exists('symfony.httpkernel.kernel.handle')->withChildren([ + SpanAssertion::exists('symfony.httpkernel.kernel.boot'), + SpanAssertion::exists('symfony.kernel.handle')->withChildren([ + SpanAssertion::exists('symfony.kernel.request'), + SpanAssertion::exists('symfony.kernel.controller'), + SpanAssertion::exists('symfony.kernel.controller_arguments'), + SpanAssertion::build( + 'symfony.controller', + 'test_symfony_70', + 'web', + 'App\Controller\CommonScenariosController::simpleViewAction' + )->withExactTags([ + Tag::COMPONENT => 'symfony', + ])->withChildren([ + SpanAssertion::build( + 'symfony.templating.render', + 'test_symfony_70', + 'web', + 'Twig\Environment twig_template.html.twig' + )->withExactTags([ + Tag::COMPONENT => 'symfony', + ]), + ]), + SpanAssertion::exists('symfony.kernel.response'), + SpanAssertion::exists('symfony.kernel.finish_request'), + ]), + ]), + ]), + ], + 'A GET request with an exception' => [ + SpanAssertion::build( + 'symfony.request', + 'test_symfony_70', + 'web', + 'error' + )->withExactTags([ + 'symfony.route.action' => 'App\Controller\CommonScenariosController@errorAction', + 'symfony.route.name' => 'error', + 'http.method' => 'GET', + 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.status_code' => '500', + Tag::SPAN_KIND => 'server', + Tag::COMPONENT => 'symfony', + ]) + ->setError('Exception', 'An exception occurred') + ->withExistingTagsNames(['error.stack']) + ->withChildren([ + SpanAssertion::exists('symfony.kernel.terminate'), + SpanAssertion::exists('symfony.httpkernel.kernel.handle') + ->setError('Exception', 'An exception occurred') + ->withExistingTagsNames(['error.stack']) + ->withChildren([ + SpanAssertion::exists('symfony.httpkernel.kernel.boot'), + SpanAssertion::exists('symfony.kernel.handle')->withChildren([ + SpanAssertion::exists('symfony.kernel.request'), + SpanAssertion::exists('symfony.kernel.controller'), + SpanAssertion::exists('symfony.kernel.controller_arguments'), + SpanAssertion::build( + 'symfony.controller', + 'test_symfony_70', + 'web', + 'App\Controller\CommonScenariosController::errorAction' + )->withExactTags([ + Tag::COMPONENT => 'symfony', + ]) + ->setError('Exception', 'An exception occurred') + ->withExistingTagsNames(['error.stack']), + SpanAssertion::exists('symfony.kernel.handleException')->withChildren([ + SpanAssertion::exists('symfony.kernel.exception')->withChildren([ + SpanAssertion::exists('symfony.controller'), + SpanAssertion::exists('symfony.kernel.controller'), + SpanAssertion::exists('symfony.kernel.controller_arguments'), + SpanAssertion::exists('symfony.kernel.finish_request'), + SpanAssertion::exists('symfony.kernel.request'), + SpanAssertion::exists('symfony.kernel.response'), + ]), + SpanAssertion::exists('symfony.kernel.response'), + SpanAssertion::exists('symfony.kernel.finish_request'), + ]), + ]), + ]), + ]), + ], + 'A GET request to a missing route' => [ + SpanAssertion::build( + 'symfony.request', + 'test_symfony_70', + 'web', + 'GET /does_not_exist' + )->withExactTags([ + 'http.method' => 'GET', + 'http.url' => 'http://localhost:9999/does_not_exist?key=value&', + 'http.status_code' => '404', + Tag::SPAN_KIND => 'server', + Tag::COMPONENT => 'symfony', + ])->withChildren([ + SpanAssertion::exists('symfony.kernel.terminate'), + SpanAssertion::exists('symfony.httpkernel.kernel.handle')->withChildren([ + SpanAssertion::exists('symfony.httpkernel.kernel.boot'), + SpanAssertion::exists('symfony.kernel.handle')->withChildren([ + SpanAssertion::exists('symfony.kernel.handleException')->withChildren([ + SpanAssertion::exists('symfony.kernel.finish_request'), + SpanAssertion::exists('symfony.kernel.response'), + SpanAssertion::exists('symfony.kernel.exception')->withChildren([ + SpanAssertion::exists('symfony.controller'), + SpanAssertion::exists('symfony.kernel.controller'), + SpanAssertion::exists('symfony.kernel.controller_arguments'), + SpanAssertion::exists('symfony.kernel.finish_request'), + SpanAssertion::exists('symfony.kernel.request'), + SpanAssertion::exists('symfony.kernel.response'), + ]), + ]), + SpanAssertion::exists('symfony.kernel.request') + ->setError( + 'Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException', + 'No route found for "GET /does_not_exist"' + ) + ->withExistingTagsNames(['error.stack']), + ]), + ]), + ]), + ], + ] + ); + } +} diff --git a/tests/Integrations/Symfony/V7_0/ConsoleCommandTest.php b/tests/Integrations/Symfony/V7_0/ConsoleCommandTest.php new file mode 100644 index 0000000000..90e72101d7 --- /dev/null +++ b/tests/Integrations/Symfony/V7_0/ConsoleCommandTest.php @@ -0,0 +1,11 @@ +./Integrations/Symfony/V6_2 ./Integrations/CLI/Symfony/V6_2 + + ./Integrations/Symfony/V7_0 + ./Integrations/CLI/Symfony/V7_0 + ./Integrations/Custom/Autoloaded ./Integrations/Custom/NotAutoloaded