Skip to content

Commit

Permalink
add email checker validator (#472)
Browse files Browse the repository at this point in the history
  • Loading branch information
solverat authored Aug 23, 2024
1 parent 7fbad6a commit 3b36754
Show file tree
Hide file tree
Showing 27 changed files with 545 additions and 114 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ Nothing to tell here, it's just [Symfony](https://symfony.com/doc/current/templa
- [Usage (Rendering Types, Configuration)](docs/0_Usage.md)
- [Headless Mode](docs/1_HeadlessMode.md)
- [SPAM Protection](docs/03_SpamProtection.md)
- [Double-Opt-In Feature](docs/03_SpamProtection.md)
- [Cloudflare Turnstile](docs/03_SpamProtection.md#cloudflare-turnstile)
- [FriendlyCaptcha](docs/03_SpamProtection.md#friendly-captcha)
- [Honeypot](docs/03_SpamProtection.md#honeypot)
- [ReCaptcha V3](docs/03_SpamProtection.md#recaptcha-v3)
- [Email Checker](docs/03_SpamProtection.md#email-checker)
- [Double-Opt-In Feature](docs/04_DoubleOptIn.md)
- [Output Workflows](docs/OutputWorkflow/0_Usage.md)
- [API Channel](docs/OutputWorkflow/09_ApiChannel.md)
- [Email Channel](docs/OutputWorkflow/10_EmailChannel.md)
Expand Down
4 changes: 3 additions & 1 deletion UPGRADE.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# Upgrade Notes

## 5.1.0
- **[SECURITY FEATURE]** Double-Opt-In Feature, read more about it [here](/docs/.md)
- **[SECURITY FEATURE]** Double-Opt-In Feature, read more about it [here](./docs/04_DoubleOptIn.md)
- **[SECURITY FEATURE]** Email Checker Validator [#471](https://github.com/dachcom-digital/pimcore-formbuilder/issues/471), read more about it [here](./docs/03_SpamProtection.md#email-checker)
- If you're using a custom form theme, please include the `instructions` type (`{% use '@FormBuilder/form/theme/type/instructions.html.twig' %}`)
- **[SECURITY FEATURE]** Add [friendly captcha field](/docs/03_SpamProtection.md#friendly-captcha)
- **[SECURITY FEATURE]** Add [cloudflare turnstile](/docs/03_SpamProtection.md#cloudflare-turnstile)
- **[BUGFIX]** Use Pimcore AdminUserTranslator for Editable Dialog Box [#450](https://github.com/dachcom-digital/pimcore-formbuilder/issues/450)
- **[BUGFIX]** CSV Export: Ignore mail params with empty data [#461](https://github.com/dachcom-digital/pimcore-formbuilder/issues/461)
- **[IMPROVEMENT]** Improve response message context [#416](https://github.com/dachcom-digital/pimcore-formbuilder/issues/416)
- **[IMPROVEMENT]** Improve API OC Field Mapping [#462](https://github.com/dachcom-digital/pimcore-formbuilder/issues/462)
- **[IMPROVEMENT]** Improve json response success message behaviour [#416](https://github.com/dachcom-digital/pimcore-formbuilder/issues/416)
- **[IMPROVEMENT]** Allow custom message in `DynamicMultiFileNotBlankValidator` constraint [#438](https://github.com/dachcom-digital/pimcore-formbuilder/issues/438)
Expand Down
1 change: 1 addition & 0 deletions config/install/translations/admin.csv
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"form_builder_validation_constraint.not_blank","Not Blank","NotBlank-Einschränkung"
"form_builder_validation_constraint.dynamic_multi_file_not_blank","Dynamic Multi-File Not Blank","NotBlank-Einschränkung für Multi-Datei Element"
"form_builder_validation_constraint.email","Email-Validation","Email-Validierung"
"form_builder_validation_constraint.email_checker","Email-Checker","Email-Checker"
"form_builder_validation_constraint.length","Length","Länge"
"form_builder_validation_constraint.url","Url","Url"
"form_builder_validation_constraint.regex","Regex","Regex"
Expand Down
13 changes: 13 additions & 0 deletions config/optional/config/email_checker.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
flysystem:
storages:
form_builder.email_checker.storage:
adapter: 'local'
options:
directory: '%kernel.project_dir%/var/tmp/form-builder-email-checker'

form_builder:
validation_constraints:
email_checker:
class: FormBuilderBundle\Validator\Constraints\EmailChecker
label: 'form_builder_validation_constraint.email_checker'
icon_class: form_builder_icon_validation
16 changes: 16 additions & 0 deletions config/optional/services/disposable_email_domain_checker.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
services:

_defaults:
autowire: true
autoconfigure: true
public: false

FormBuilderBundle\Validator\EmailChecker\DisposableEmailDomainChecker:
tags:
- { name: form_builder.validator.email_checker }

FormBuilderBundle\Maintenance\DisposableEmailDomainFetchTask:
arguments:
$logger: '@form_builder.application_logger.email_checker_logger'
tags:
- {name: pimcore.maintenance.task, type: formbuilder_email_checker_disposable_email_domain_fetch }
8 changes: 8 additions & 0 deletions config/optional/services/email_checker.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
services:

form_builder.application_logger.email_checker_logger:
public: true
class: Pimcore\Bundle\ApplicationLoggerBundle\ApplicationLogger
calls:
- [addWriter, ['@Pimcore\Bundle\ApplicationLoggerBundle\Handler\ApplicationLoggerDb']]
- [setComponent, ['form_builder_email_checker']]
4 changes: 3 additions & 1 deletion config/services/builder.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ services:
FormBuilderBundle\Builder\FrontendFormBuilder: ~

# backend form builder
FormBuilderBundle\Builder\ExtJsFormBuilder: ~
FormBuilderBundle\Builder\ExtJsFormBuilder:
arguments:
$translator: '@Pimcore\Bundle\AdminBundle\Translation\AdminUserTranslator'

# form values output applier
FormBuilderBundle\Form\FormValuesOutputApplier: ~
Expand Down
6 changes: 1 addition & 5 deletions config/services/event.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,4 @@ services:

FormBuilderBundle\EventListener\Admin\AssetListener:
tags:
- { name: kernel.event_subscriber }

FormBuilderBundle\EventListener\Core\CleanUpListener:
tags:
- {name: pimcore.maintenance.task, type: formbuilder_clean_up }
- { name: kernel.event_subscriber }
10 changes: 10 additions & 0 deletions config/services/maintenance.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
services:

_defaults:
autowire: true
autoconfigure: true
public: false

FormBuilderBundle\Maintenance\CleanUpTask:
tags:
- {name: pimcore.maintenance.task, type: formbuilder_clean_up }
14 changes: 13 additions & 1 deletion config/services/validator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,16 @@ services:
FormBuilderBundle\Validator\Constraints\FriendlyCaptchaValidator:
public: false
tags:
- { name: validator.constraint_validator }
- { name: validator.constraint_validator }

FormBuilderBundle\Validator\Constraints\EmailCheckerValidator:
public: false
tags:
- { name: validator.constraint_validator }

#
# Email Checker

FormBuilderBundle\Validator\EmailChecker\EmailCheckerProcessor:
arguments:
$emailChecker: !tagged_iterator form_builder.validator.email_checker
51 changes: 50 additions & 1 deletion docs/03_SpamProtection.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,53 @@ form_builder:

3. Add the "Cloudflare Turnstile" field to your form
4. Enable the CloudFlareTurnstile [javascript module](./91_Javascript.md)
4. Done
4. Done

## Email Checker
The Email Checker Validator is available, if you've added at least one service. Per default, no service is registered by default.

This validator includes all services tagged with `form_builder.validator.email_checker`.
If one of those services returns false in `isValid()` method, the validator will fail.

### [BUILT IN] Disposable Email Domain Checker
If enabled, this checker will fetch every 24h a database (stored in `%kernel.project_dir%/var/tmp/form-builder-email-checker` via flysystem) with known disposable mail hosts from [disposable/disposable](https://github.com/disposable/disposable).
After that, the validator will check the given domain of an email address against the database.

This service is not available per default and needs to be enabled if you want to use it:

```yaml
form_builder:
spam_protection:
email_checker:
disposable_email_domains:
enabled: true
include_subdomains: false # Also search host as subdomain. Default: false. Note, that this can be a huge performance impact
```
### Create custom Email Checker
```yaml
services:
App\Validator\EmailChecker\MyEmailChecker:
tags:
- { name: form_builder.validator.email_checker }
```
```php
<?php

namespace App\Validator\EmailChecker;

use FormBuilderBundle\Configuration\Configuration;
use League\Flysystem\FilesystemOperator;
use function Symfony\Component\String\u;

final class MyEmailChecker implements EmailCheckerInterface
{
public function isValid(string $email, array $context): bool
{
// do your validation here
return true;
}
}
```
3 changes: 2 additions & 1 deletion docs/04_DoubleOptIn.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ Additional Info:
- Additional fields will be stored as array in the DoubleOptInSession in `additionalData`

## Trash-Mail Protection
TBD
The `EmailChecker` Validator is automatically appended to the `emailAddress` field.
This validator only triggers, if you've configured at least one email checker service - read more about it [here](./docs/03_SpamProtection.md#email-checker)
14 changes: 14 additions & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,20 @@ private function buildSpamProductionNode(): NodeDefinition
->scalarNode('secret_key')->defaultNull()->end()
->end()
->end()

->arrayNode('email_checker')
->addDefaultsIfNotSet()
->children()
->arrayNode('disposable_email_domains')
->addDefaultsIfNotSet()
->children()
->booleanNode('enabled')->defaultFalse()->end()
->booleanNode('include_subdomains')->defaultFalse()->end()
->end()
->end()
->end()
->end()

->end();

return $rootNode;
Expand Down
23 changes: 23 additions & 0 deletions src/DependencyInjection/FormBuilderExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public function prepend(ContainerBuilder $container): void
'form_builder_spam_protection_recaptcha_v3_site_key' => $config['spam_protection']['recaptcha_v3']['site_key'],
],
]);

$this->buildEmailCheckerStack($container, $config);
}

/**
Expand Down Expand Up @@ -64,5 +66,26 @@ public function load(array $configs, ContainerBuilder $container): void

$container->setParameter('form_builder.persistence.doctrine.enabled', true);
$container->setParameter('form_builder.persistence.doctrine.manager', $entityManagerName);

}

private function buildEmailCheckerStack(ContainerBuilder $container, array $config): void
{
$enabled = false;
$loader = new YamlFileLoader($container, new FileLocator([__DIR__ . '/../../config/optional']));

if ($config['spam_protection']['email_checker']['disposable_email_domains']['enabled'] === true) {
$enabled = true;
$loader->load('services/disposable_email_domain_checker.yaml');
} elseif (count($container->findTaggedServiceIds('form_builder.validator.email_checker')) > 0) {
$enabled = true;
}

if ($enabled === false) {
return;
}

$loader->load('config/email_checker.yaml');
$loader->load('services/email_checker.yaml');
}
}
82 changes: 82 additions & 0 deletions src/Event/BaseSubmissionEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace FormBuilderBundle\Event;

use Symfony\Contracts\EventDispatcher\Event;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;

abstract class BaseSubmissionEvent extends Event
{
protected readonly Request $request;
protected FormInterface $form;
protected bool $useFlashBag;
protected array $messages;

protected ?string $locale = null;

public function __construct(
Request $request,
FormInterface $form,
bool $useFlashBag = true,
array $messages = []
) {
$this->request = $request;
$this->form = $form;
$this->useFlashBag = $useFlashBag;
$this->messages = $messages;
}

public function getRequest(): Request
{
return $this->request;
}

public function getForm(): FormInterface
{
return $this->form;
}

public function useFlashBag(): bool
{
return $this->useFlashBag;
}

public function getMessages(): array
{
return $this->messages;
}

public function getMessagesOfType(string $type): array
{
return $this->messages[$type] ?? [];
}

public function hasMessagesOfType(string $type): bool
{
return array_key_exists($type, $this->messages);
}

public function addMessage(string $type, mixed $message): void
{
if (empty($message)) {
return;
}

if (!array_key_exists($type, $this->messages)) {
$this->messages[$type] = [];
}

$this->messages[$type][] = $message;
}

public function getLocale(): ?string
{
return $this->locale;
}

public function setLocale(?string $locale): void
{
$this->locale = $locale;
}
}
Loading

0 comments on commit 3b36754

Please sign in to comment.