decoupled_json_log-1.0.x-dev/src/Plugin/Validation/Constraint/RateLimitPerUserValidator.php
src/Plugin/Validation/Constraint/RateLimitPerUserValidator.php
<?php
declare(strict_types=1);
namespace Drupal\decoupled_json_log\Plugin\Validation\Constraint;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\decoupled_json_log\Exception\DecoupledJsonLogException;
use Drupal\decoupled_json_log\LogJsonInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Webmozart\Assert\Assert;
/**
* Validates the RateLimitPerUser constraint.
*/
final class RateLimitPerUserValidator extends ConstraintValidator implements ContainerInjectionInterface {
public function __construct(
protected AccountInterface $currentUser,
protected EntityTypeManagerInterface $entityTypeManager,
protected TimeInterface $time,
protected ConfigFactoryInterface $configFactory,
) {
}
/**
* {@inheritdoc}
*/
#[\Override]
public static function create(ContainerInterface $container): static {
return new self(
$container->get('current_user'),
$container->get('entity_type.manager'),
$container->get('datetime.time'),
$container->get('config.factory'),
);
}
/**
* {@inheritdoc}
*/
#[\Override]
public function validate(mixed $value, Constraint $constraint): void {
if (is_object($value)) {
if (method_exists($value, 'label')) {
$name = $value->label();
}
else {
throw new DecoupledJsonLogException('Value does not have label() method!');
}
if ($value instanceof LogJsonInterface && is_string($name) && !$this->isPostCountBelowLimit()) {
if ($constraint instanceof RateLimitPerUser) {
$this->context->addViolation($constraint->postIntervalExceeded, ['%value' => $value->label()]);
}
else {
throw new DecoupledJsonLogException('Failed to set constraint RateLimitPerUser!');
}
}
}
else {
throw new DecoupledJsonLogException('Value must be an object!');
}
}
/**
* Checks whether the current user's log post count is below the limit.
*
* @returns bool
* TRUE if the post count is below the limit.
*/
private function isPostCountBelowLimit(): bool {
$uid = $this->currentUser->id();
$log_json_storage = $this->entityTypeManager->getStorage('log_json');
$interval_seconds = $this->configFactory->get('decoupled_json_log.settings')->get('rate_limit.interval_seconds');
Assert::integer($interval_seconds, 'Failed to get interval_seconds config!');
$current_timestamp = $this->time->getCurrentTime();
$rate_limit_interval_start = $current_timestamp - $interval_seconds;
$log_json_query = $log_json_storage->getQuery()
->accessCheck(FALSE)
->condition('uid', $uid)
->condition('created', $rate_limit_interval_start, '>=');
$log_json_count = $log_json_query->count()->execute();
if ($this->currentUser->isAnonymous()) {
$rate_limit = $this->configFactory->get('decoupled_json_log.settings')->get('rate_limit.count_anon');
}
else {
$rate_limit = $this->configFactory->get('decoupled_json_log.settings')->get('rate_limit.count_auth');
}
Assert::integer($rate_limit, 'Could not get config rate_limit!');
return $log_json_count <= $rate_limit;
}
}
